[evolved] library Keyboard-emulator for german keyboard-layout (old title: "printing" characters that have a multibyte representation to a keyboard-emulator does not work like write)

Hi Everybody,

for deriving a USB-keyboard-library for a german keyboard-layout
I'm modifying the standard keyboard.h / keyboard.cpp-library

The original library can use keyboard.print(). The german keyboard-layout
has some characters that are represented as multibyte values
example character "ä" is represented by
first byte 195
second byte 164

The Euro-symbol "€" has even a three byte representation

const byte euro_1stByte = 226;
const byte euro_2ndByte = 130;
const byte euro_3rdByte = 172;

How does the function "print" manage such characters?
print is able to print them correctly to the serial monitor.
but I don't know how it is doing this.

Because for example if you analyse

String myStr = "€";

you get
myStr[0] holds value 226
myStr[1] holds value 130
myStr[2] holds value 172

The "€"-symbol is not represented by a single byte but the function print handles them correctly? How does the function print do this???

I'm asking this because transforming characters to USB-HID-keyboard "key-presses" works this way
take a character (a single byte) and setup the so called "KeyReport"
to the matching values
example "A"
KeyReport.key = 0x04;
KeyReport.modifier = KEY_SHIFT;

send out on the USB a "keypress-command" and a "key-release-command"

with characters that are represented by 2 or 3 bytes it can't be done this way
scanning a string byte for byte does not work because some characters have 2 or 3 bytes. So where = in which library is the code for the "print"-command?
I haven't found it in Serial.h / Serial.cpp

Serial.h has a single line of code

		using Print::write; // pull in write(str) and write(buf, size) from Print

Due to my limited knowledge about C++ I don't know where to look.

best regards Stefan

Hello

print will just send those 3 bytes, it doesn't care what it is. The Serial Monitor recognize those 3 bytes as unicode character '€'

Here is an example to calculate the length of a utf8 character Bh8bos - Online C++ Compiler & Debugging Tool - Ideone.com

Does this mean the decoding of the bytesequence is done by the serial monitor?
In more detail: if the serial monitor receives a bytesequence of decimal 226,130,172 this byte-sequence gets decoded by the serial monitor into a single character "€" ?

Sending simulated keystrokes over USB to a computer does not work with sending 2 or 3 bytes. For the USB-keystrokes the characters get translated into a single dataset.

This means if the keyboard-print-function receives a first value of decimal 226 the print-function would have to look up the next (i.e. byte at position i+1 and byte at position i+2 and then depending on the 2nd and 3rd byte create that one single keyreport that is the keystroke for this special character.

This means I have to find the file where the function "print" and "println" is defined.

I guess print is based on write(a single byte) with a loop
as pseudo-code

do write(a single byte) until byte == 0

best regards Stefan

Why not try it?

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.write(226);
  Serial.write(130);
  Serial.write(172);
  Serial.println();
}

void loop() {
}

Print.h / Print.cpp

print handles unicode characters by just blindly sending all bytes of the character, and the receiving device has to handle the actual recognition of unicode.

You will need to write code for the keyboard library that can detect a unicode sequence and take the appropriate action to send as a key sequence.

Time to read up on UTF-8. That is the format used for Unicode characters in the Arduino IDE. The IDE just puts the UTF-8 encoding of the Unicode character into your string. When it is sent to the Serial Monitor it is sent as three characters. When the three arrive, they are passed to the display system as part of a stream of characters and it is the display system that recognizes the UTF-8 sequence and displays the correct Unicode character.

The euro symbol is saved as three bytes: "\xE2\x82\xAC". When being displayed, this translates to the Unicode character 0x20AC (euro sign).

On the German keyboard, the euro sign is AltGr-e. When you see a "\xE2" character in your string to be typed, check to see what the next two characters are. If they are "\x82\xAC" you would send the keycode for 'e' with the AltGr modifier. The Windows OS will see that and convert it into the Unicode character for display.

Hi John,

thank you very much for explaining it.
I did do that and finally I have a working version that is able to print all keyboard-characters of the german keyboard-layout.

I have NO knowledge about C++-classes.
This means I have a function typeText(const char text)* that does the charactor-to-report-key-value-translation that is placed in my *.ino-file.

I have no knowledge about what I would have to change to make the function typetext a - is it called member? - of the Keyboard-"class".
Or even better to have a function print and println so it could be used the most common way like in so many other libraries like lcd.print etc.

best regards Stefan

It's not clear what you're asking for. Maybe nothing?

Are you satisfied with the way you implemented typeText() as a free function?

Are you looking for resources for learning about C++ classes?

Are you looking for example code of classes that implement the print and println features? If so, then look at any of the libraries you've mentioned ... lcd, keyboard, etc. Their source code along with Print.h / Print.cpp are complete examples.

Do you want someone to write the code for you?

Hi gfvavlo,

you are correct I didn't posted a specific question.

Sure the libraries themselves are examples. I consider learning the basic structure of a class / a library only from looking at examples to be very in-effective.

My former experience with "tutorials" is: 98% hard to understand because not adapted to a newcomer-level.

To find that 2% that explains things in a way that is easy to undestand for newcomers takes a lot of time.

I will post this question as a new thread because it is a much more generalised question than the Keyboard-emulator-thing.

Anyway if somebody really enjoys it to explain the basics of writing / modifying a library and/or a a class shown based on my KeyboardGER-library I really apreciate it.

best regards Stefan

Not an explanation but an example of what to do:

// This part is the KeyboardGER.h file:
#pragma once
#include <Arduino.h>  // This incliudes the Print class

class KeyboardGER : public Print
{
  public:
    size_t write(uint8_t);
};

// This part is the KeyboardGER.cpp file
// Put "#include  <KeyboardGER.h>" at the top
size_t  KeyboardGER::write(uint8_t c)
{
  // This is where you would send one character and
  // return the number of characters sent.
  return 1;
}

// Below is the example sketch.
// Put "#include <KeyboardGER.h>" at the top
KeyboardGER Keyboard;

void setup()
{
  Keyboard.println("This is a test.");
}

void loop() {}

Hi John,

thank you for posting the principle. In the meantime I have started a different thread with a more general question about how to write libraries here:

And finally managed to make the library compile.
sigh! But now there is a functional bug which I have to narrow down.

best regards Stefan

OK so finally I can present version 0.1 of my first self-written library a keyboard-emulator for the german keyboard-layout.

It will send the correct keypress-codes for every character that is on a german keyboard.
Including the characters ° ^ ² ³ { [ ] } \ ~ @ € | ä ö ü _ Ä Ö Ü µ ß
which are special to the german keyboard-layout and all other characters where some of them have a different key on the keyboard compared to the US-keyboard-layout
functional keys like "F1", "F2" backspace, cursor up/down etc. are not included. Except the Return-Key.

This means the main functionality is to call a function named "typetext"
to do what the name says.

The headerfile KeyboardGER2.h

/*
  KeyboardGER2.h
  derived from Keyboard.h and modified by user StefanL38

  Copyright (c) 2015, Arduino LLC
  Original code (pre-library): Copyright (c) 2011, Peter Barrett

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifndef KEYBOARDGER2_h  // this #ifndef #define prevents from re-declaration-errors 
#define KEYBOARDGER2_h  // through accidentically "double"-including KeyboardGER2.h


#include "Arduino.h"
#include "HID.h"

#if !defined(_USING_HID)

#warning "Using legacy HID core (non pluggable)"

#endif

const byte Ctrl  = 1;
const byte Shift = 2;
const byte Alt   = 4;

const byte CtrlAlt   = Ctrl + Alt;
const byte AltCtrl   = CtrlAlt;

const byte aeoeue_1stByte = 195;
const byte ae_2ndByte = 164;
const byte oe_2ndByte = 182;
const byte ue_2ndByte = 188;

const byte Ae_2ndByte = 132;
const byte Oe_2ndByte = 150;
const byte Ue_2ndByte = 156;
const byte SharpS_2ndByte = 159;

const byte mue23_1stByte = 194;
const byte mue_2ndByte   = 181;
const byte mue2_2ndByte  = 178;
const byte mue3_2ndByte  = 179;

const byte degree_2ndByte = 176;
const byte paragraph_2ndByte = 167;

const byte euro_1stByte = 226;
const byte euro_2ndByte = 130;
const byte euro_3rdByte = 172;

const int SendDelay = 5;

/* Modifiers */
enum MODIFIER_KEY {
	KEY_CTRL     = 1,
	KEY_SHIFT    = 2,
	KEY_ALT      = 4,
    KEY_CTRL_ALT = 5
};


typedef struct {
	unsigned char key;
	unsigned char modifier;
	} KEY_MOD_VALUE;


typedef struct {
  uint8_t modifiers;
  uint8_t reserved;
  uint8_t pressedKeys[6];
} KeyReport_t;



/* German keyboard (as HID standard) */
#define KEY_MOD_TABLE_SIZE (127)

const KEY_MOD_VALUE key_mod_values[KEY_MOD_TABLE_SIZE] = {
   {0x00, 0 }, /* 0  */
   {0x00, 0 }, /* 1  */
   {0x00, 0 }, /* 2  */
   {0x00, 0 }, /* 3  */
   {0x00, 0 }, /* 4  */
   {0x00, 0 }, /* 5  */
   {0x00, 0 }, /* 6  */
   {0x00, 0 }, /* 7  */
   {0x00, 0 }, /* 8  */
   {0x00, 0 }, /* 9  */
   {0x58, 0 }, /* 10  */  //translate newline to returnkey
   {0x00, 0 }, /* 11  */
   {0x00, 0 }, /* 12  */
   {0x58, 0 }, /* 13  */  // translate return to returnkey
   {0x00, 0 }, /* 14  */
   {0x00, 0 }, /* 15  */
   {0x00, 0 }, /* 16  */
   {0x00, 0 }, /* 17  */
   {0x00, 0 }, /* 18  */
   {0x00, 0 }, /* 19  */
   {0x00, 0 }, /* 20  */
   {0x00, 0 }, /* 21  */
   {0x00, 0 }, /* 22  */
   {0x00, 0 }, /* 23  */
   {0x00, 0 }, /* 24  */
   {0x00, 0 }, /* 25  */
   {0x00, 0 }, /* 26  */
   {0x00, 0 }, /* 27  */
   {0x00, 0 }, /* 28  */
   {0x00, 0 }, /* 29  */
   {0x00, 0 }, /* 30  */
   {0x00, 0 }, /* 31  */
   {0x00, 0 }, /* 32  */
   {0x1E, KEY_SHIFT }, /* 33   !  */
   {0x1F, KEY_SHIFT }, /* 34   "  */
   {0x31, 0 }, /* 35   #  */
   {0x21, KEY_SHIFT }, /* 36   $  */
   {0x22, KEY_SHIFT }, /* 37   %  */
   {0x23, KEY_SHIFT }, /* 38   &  */
   {0x31, KEY_SHIFT }, /* 39   '  */
   {0x25, KEY_SHIFT }, /* 40   (  */
   {0x26, KEY_SHIFT }, /* 41   )  */
   {0x30, KEY_SHIFT }, /* 42   * */
   {0x30, 0 }, /* 43   +  */
   {0x36, 0 }, /* 44   ,  */
   {0x38, 0 }, /* 45   -  */
   {0x37, 0 }, /* 46   .  */
   {0x24, KEY_SHIFT }, /* 47   / */
   {0x27, 0 }, /* 48 0 */
   {0x1E, 0 }, /* 49 1 */
   {0x1F, 0 }, /* 50 2 */
   {0x20, 0 }, /* 51 3 */     // {0x20, 0 }, /* 51 3 */
   {0x21, 0 }, /* 52 4 */
   {0x22, 0 }, /* 53 5 */
   {0x23, 0 }, /* 54 6 */
   {0x24, 0 }, /* 55 7 */
   {0x25, 0 }, /* 56 8 */
   {0x26, 0 }, /* 57 9 */
   {0x37, KEY_SHIFT }, /* 58   :  */
   {0x36, KEY_SHIFT }, /* 59   ; */
   {0x64, 0 }, /* 60   <  */
   {0x27, KEY_SHIFT }, /* 61   =  */
   {0x64, KEY_SHIFT }, /* 62   >  */
   {0x2D, KEY_SHIFT }, /* 63   ?  */
   {0x14, KEY_CTRL_ALT }, /* 64   @ */
   {0x04, KEY_SHIFT }, /* 65   A  */
   {0x05, KEY_SHIFT }, /* 66   B  */
   {0x06, KEY_SHIFT }, /* 67   C  */
   {0x07, KEY_SHIFT }, /* 68   D  */
   {0x08, KEY_SHIFT }, /* 69   E */
   {0x09, KEY_SHIFT }, /* 70   F  */
   {0x0A, KEY_SHIFT }, /* 71   G  */
   {0x0B, KEY_SHIFT }, /* 72   H  */
   {0x0C, KEY_SHIFT }, /* 73   I */
   {0x0D, KEY_SHIFT }, /* 74   J  */
   {0x0E, KEY_SHIFT }, /* 75   K  */
   {0x0F, KEY_SHIFT }, /* 76   L  */
   {0x10, KEY_SHIFT }, /* 77   M  */
   {0x11, KEY_SHIFT }, /* 78   N  */
   {0x12, KEY_SHIFT }, /* 79   O */
   {0x13, KEY_SHIFT }, /* 80   P */
   {0x14, KEY_SHIFT }, /* 81   Q */
   {0x15, KEY_SHIFT }, /* 82   R */
   {0x16, KEY_SHIFT }, /* 83   S  */
   {0x17, KEY_SHIFT }, /* 84   T */
   {0x18, KEY_SHIFT }, /* 85   U */
   {0x19, KEY_SHIFT }, /* 86   V  */
   {0x1A, KEY_SHIFT }, /* 87   W */
   {0x1B, KEY_SHIFT }, /* 88   X  */
   {0x1D, KEY_SHIFT }, /* 89   Y  */
   {0x1C, KEY_SHIFT }, /* 90   Z */
   {0x25, KEY_CTRL_ALT }, /* 91   [ */
   {0x2D, KEY_CTRL_ALT }, /* 92   \ */
   {0x26, KEY_CTRL_ALT }, /* 93   ] */
   {0x35, 0 }, /* 94   ^  */
   {0x38, KEY_SHIFT }, /* 95   _  */
   {0x2E, KEY_SHIFT }, /* 96   `  */
   {0x04, 0 }, /* 97   a  */
   {0x05, 0 }, /* 98   b  */
   {0x06, 0 }, /* 99   c  */
   {0x07, 0 }, /* 100   d  */
   {0x08, 0 }, /* 101   e  */
   {0x09, 0 }, /* 102   f  */
   {0x0A, 0 }, /* 103   g  */
   {0x0B, 0 }, /* 104   h  */
   {0x0C, 0 }, /* 105   i  */
   {0x0D, 0 }, /* 106   j  */
   {0x0E, 0 }, /* 107   k  */
   {0x0F, 0 }, /* 108   l  */
   {0x10, 0 }, /* 109   m  */
   {0x11, 0 }, /* 110   n  */
   {0x12, 0 }, /* 111   o  */
   {0x13, 0 }, /* 112   p  */
   {0x14, 0 }, /* 113   q  */
   {0x15, 0 }, /* 114   r  */
   {0x16, 0 }, /* 115   s  */
   {0x17, 0 }, /* 116   t  */
   {0x18, 0 }, /* 117   u  */
   {0x19, 0 }, /* 118   v  */
   {0x1A, 0 }, /* 119   w  */
   {0x1B, 0 }, /* 120   x  */
   {0x1D, 0 }, /* 121   y  */
   {0x1C, 0 }, /* 122   z  */
   {0x0 , 0 }, /* 123   { */
   {0x64, KEY_CTRL_ALT }, /* 124   | */
   {0x0 , 0 }, /* 125   } */
   {0x30, KEY_CTRL_ALT }, /* 126   ~ */
};



class KeyboardGER2 
{
//private: {}
public:
  KeyReport_t _keyReport;
  void sendReport(KeyReport_t* keys);
  KeyboardGER2(void);
  void begin(void);
  void end(void);
  void sendKeyPress(uint8_t p_key, uint8_t p_modifiers);
  void sendKeyRelease();
  void SendReturn();
  void typeText(const char* text);

};

#endif

the CPP-file KeyboardGER2.cpp

/*
  KeyboardGER2.cpp
  derived from Keyboard.cpp and modified by user StefanL38

  Copyright (c) 2015, Arduino LLC
  Original code (pre-library): Copyright (c) 2011, Peter Barrett

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

  ATTENTION ! for easier writing down code there is a convention to give
  the class and the constructor-function the exact same name as the filenames *.h / *.cpp
  in this case this is "KeyboardGER2"
*/

#include "KeyboardGER2.h"

#if defined(_USING_HID)


static const uint8_t _hidReportDescriptor[] PROGMEM = {

  //  Keyboard
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)  // 47
    0x09, 0x06,                    // USAGE (Keyboard)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x02,                    //   REPORT_ID (2)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
   
  0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,                    //   REPORT_SIZE (1)
    
  0x95, 0x08,                    //   REPORT_COUNT (8)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x95, 0x01,                    //   REPORT_COUNT (1)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
    
  0x95, 0x06,                    //   REPORT_COUNT (6)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    0x25, 0x73,                    //   LOGICAL_MAXIMUM (115)
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    
  0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    0x29, 0x73,                    //   USAGE_MAXIMUM (Keyboard Application)
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    0xc0,                          // END_COLLECTION
};


KeyboardGER2::KeyboardGER2(void) { //<= the first "KeyboardGER2::" is the class-NAME the second "KeyboardGER2" is the functionname of the constructor
	static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));
	HID().AppendDescriptor(&node);
}

void KeyboardGER2::begin(void){ } // insert class-Name:: between returntype and function-name 

void KeyboardGER2::end(void) { }

void KeyboardGER2::sendReport(KeyReport_t* keys){
	HID().SendReport(2,keys,sizeof(KeyReport_t));
}


void KeyboardGER2::sendKeyPress(uint8_t p_key, uint8_t p_modifiers) {
  
  KeyReport_t myKeyMessage;
  
  for (int j = 0; j < 6; j++) {
    myKeyMessage.pressedKeys[j] = 0;
  }
  
  myKeyMessage.pressedKeys[0] = p_key;
  myKeyMessage.modifiers = p_modifiers;
  myKeyMessage.reserved  = 0;

  sendReport(&myKeyMessage);  
  delay(SendDelay);  
}


void KeyboardGER2::sendKeyRelease() {

  KeyReport_t myKeyMessage;

  for (int j = 0; j < 6; j++) {
    myKeyMessage.pressedKeys[j] = 0;
  }

  myKeyMessage.pressedKeys[0] = 0;
  myKeyMessage.modifiers = 0;
  myKeyMessage.reserved  = 0;
  
  sendReport(&myKeyMessage);
}


void KeyboardGER2::SendReturn() {
  sendKeyPress(88,0);
  sendKeyRelease();  
}


// as some special characters like "@" "Ä", "€" have a two-byte or even three-byte-representation
// a simple write (uint8_t that_one_single_byte) is NOT possible 
// the multibyte characters need a more special analyses to set the correspnding key-press-value
void KeyboardGER2::typeText(const char* text) {

  uint8_t ASCII_Code;
  uint8_t SecondByte;
  uint8_t ThirdByte;
  
  uint8_t key;
  uint8_t modifiers;

  int len = strlen(text);
  int i = -1; // initialise with -1 to get i = 0 at first run with i++ at the top of the loop

  while (i < len) {
    i++;
    // translate character to key combination
    ASCII_Code = (uint8_t)text[i];

    if (ASCII_Code > KEY_MOD_TABLE_SIZE) { // character has a multibyte representation
      switch (ASCII_Code) {
        case aeoeue_1stByte: // if it's an äöü
          i++;
          SecondByte = text[i];

          if (SecondByte == ae_2ndByte) { // if it's an ä
            key = 52; 
            modifiers   = 0;
            break;
          }

          if (SecondByte == oe_2ndByte) { // if it's an ö
            key = 51; 
            modifiers   = 0;
            break;
          }

          if (SecondByte == ue_2ndByte) { // if it's an ü
            key = 47; 
            modifiers   = 0;
            break;
          }

          if (SecondByte == Ae_2ndByte) { // if it's an Ä
            key = 52; 
            modifiers   = Shift;
            break;
          }

          if (SecondByte == Oe_2ndByte) { // if it's an Ö
            key = 51; 
            modifiers   = Shift;
            break;
          }

          if (SecondByte == Ue_2ndByte) { // if it's an Ü
            key = 47; 
            modifiers   = Shift;
            break;
          }

          if (SecondByte == SharpS_2ndByte) { // if it's an ß
            key = 45; 
            modifiers   = 0;
            break;
          }
          
        case mue23_1stByte: // if it's a µ²³
          i++;
          SecondByte = text[i];

          if (SecondByte == mue_2ndByte) { // if it's an µ
            key = 16; 
            modifiers   = CtrlAlt;
            break;
          }

          if (SecondByte == mue2_2ndByte) { // if it's an ²
            key = 31; 
            modifiers   = CtrlAlt;
            break;
          }

          if (SecondByte == mue3_2ndByte) { // if it's an ³
            key = 32; 
            modifiers   = CtrlAlt;
            break;
          }

          if (SecondByte == degree_2ndByte) { // if it's an °
            key = 53; 
            modifiers   = Shift;
            break;
          }

          if (SecondByte == paragraph_2ndByte) { // if it's an §
            key = 32; 
            modifiers   = Shift;
            break;
          }

        case euro_1stByte: // if it's a €-symbol
          i++;
          SecondByte = text[i];
          if (SecondByte == euro_2ndByte) {
            i++;
            ThirdByte = text[i];
            if (ThirdByte == euro_3rdByte) {
              key = 8; 
              modifiers   = CtrlAlt;
              break;
            }
          }
        default:
          key = 0; 
          modifiers      = 0;
          //dbg("default", ASCII_Code);
          break;
      }
    } //if (ASCII_Code > KEY_MOD_TABLE_SIZE) multibyte representation

    else { // character has a single-byte representation
      KEY_MOD_VALUE myKeyAndMod = key_mod_values[ASCII_Code];
      modifiers = myKeyAndMod.modifier;

      key = myKeyAndMod.key; // myKeyMessage.pressedKeys[0] = myKeyAndMod.key;
    } //  else { // character has a single-byte representation

    sendKeyPress(key,modifiers);
    sendKeyRelease();  
  } //while (i < len)

} // void typeText(const char* text)

#endif

My code to test the keyboard-emulator does receive characters over a second serial connection on a Seeeduino-XIAO which has a second hardware serial interface.
This code makes use of some other libraries but at least you can get a glimpse of how the keyboard-emulator can be used

#include <DebugTxtVar.h>
#include <KeyboardGER2.h>

KeyboardGER2 myKeyBoard;

#include <SafeString.h>
createSafeString(myDemo_SS, 256);
createSafeString(myReceiveBuffer_SS, 256);
createSafeString(myReceivedChars_SS, 256);
createSafeString(delimiters, 3, "|" );
createSafeString(One_ID_Val, 256);
createSafeString(MyEmail_SS, 64);
createSafeString(MyPassword_SS, 64);
createSafeString(MyOTP_SS, 64);

const byte D0 = 0;
const byte D1 = 1;
const byte D2 = 2;

byte Button0;
byte Button1;
byte Button2;

unsigned long MyTestTimer = 0;                   // variables MUST be of type unsigned long
unsigned long MyButtonTimer = 0;                   // variables MUST be of type unsigned long
unsigned long KeyTimer = 0;


const byte lowValLimit = 31;
boolean stringReceived = false;

unsigned long MyTimer = 0;
unsigned long MyPeriod = 500; // 500 milliseconds ON and OFF  result: 1 blink per second


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println(__FILE__);
  Serial.print( F("  compiled ") );
  Serial.print(__DATE__);
  Serial.print( F(" ") );
  Serial.println(__TIME__);
}


boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - periodStartTime >= TimePeriod ) {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

//unsigned long MyTestTimer =  0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}

void setup() {
  pinMode(D0, INPUT_PULLUP);
  pinMode(D1, INPUT_PULLUP);
  pinMode(D2, INPUT_PULLUP);
  delay(1000);
  Serial.begin(115200);
  delay(1000);
  Serial.println( F("Setup-Start") );
  Serial1.begin(115200);
  Serial.println( F("Serial1.begin(115200) done") );

  PrintFileNameDateTime();
  myKeyBoard.begin();

  myReceiveBuffer_SS = "";
  myReceivedChars_SS = "";
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);

  stringReceived = recvWithEndMarker(); // as long as no CR is entered returns false

  if (stringReceived) {
    stringReceived = false;
    dbg("received", myReceivedChars_SS);
    myReceivedChars_SS.nextToken(One_ID_Val, delimiters);
    MyEmail_SS = One_ID_Val;
    MyEmail_SS += '\n';
    dbg("received", MyEmail_SS);
    myKeyBoard.typeText(MyEmail_SS.c_str() );
    myKeyBoard.SendReturn();

    myReceivedChars_SS.nextToken(One_ID_Val, delimiters);
    MyPassword_SS = One_ID_Val;
    MyPassword_SS += '\r';
    dbg("received", MyPassword_SS);
    myKeyBoard.typeText(MyPassword_SS.c_str() );
    myKeyBoard.SendReturn();

    myReceivedChars_SS.nextToken(One_ID_Val, delimiters);
    MyOTP_SS = One_ID_Val;
    dbg("received", MyOTP_SS);
    myKeyBoard.typeText(MyOTP_SS.c_str() );
    myKeyBoard.SendReturn();
  }

}


boolean recvWithEndMarker() {

  boolean result = false;

  char endMarker = '\n';
  char rc;

  if (Serial1.available() > 0) { // whenever a byte is received
    rc = Serial1.read();       // read byte from receive-buffer

    if (rc != endMarker) {     // if it's not the endmarker
      if (rc > lowValLimit ) {    // is a printable character &&
        myReceiveBuffer_SS += rc; // store byte

      }
    }
    else { // endmarker
      myReceivedChars_SS = myReceiveBuffer_SS;
      myReceiveBuffer_SS = "";
      result = true;
    }
  }
  return result; // give back number
}

/*
 * 
 */

EDIT: Here is a picture of a keyboard with a german keyboard layout

best regards Stefan

Because I fairly often see questions about using the Keyboard library on systems with non-US keyboard layouts, I tried my hand a making an international Keyboard library. It layers over the current Keyboard and provides both keycode mappings for every keyboard that Microsoft Windows supports and support for UTF-8 encoded Unicode characters in strings.

@johnwasser :wink:

I downloaded and installed the library-

I'm using a portable installation
But I get this compiler-error

F:\MyPortable-PRgs\arduino-1.8.16\arduino-builder -dump-prefs -logger=machine -hardware F:\MyPortable-PRgs\arduino-1.8.16\hardware -hardware F:\MyPortable-PRgs\arduino-1.8.16\portable\packages -hardware F:\MyPortable-PRgs\arduino-1.8.16\portable\sketchbook\hardware -tools F:\MyPortable-PRgs\arduino-1.8.16\tools-builder -tools F:\MyPortable-PRgs\arduino-1.8.16\hardware\tools\avr -tools F:\MyPortable-PRgs\arduino-1.8.16\portable\packages -built-in-libraries F:\MyPortable-PRgs\arduino-1.8.16\libraries -libraries F:\MyPortable-PRgs\arduino-1.8.16\portable\sketchbook\libraries -fqbn=Seeeduino:samd:seeed_XIAO_m0:usbstack=arduino,debug=off -vid-pid=2886_802F -ide-version=10816 -build-path C:\Users\dipl-\AppData\Local\Temp\arduino_build_139330 -warnings=none -build-cache C:\Users\dipl-\AppData\Local\Temp\arduino_cache_332152 -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.CMSIS-Atmel.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1 -prefs=runtime.tools.CMSIS-Atmel-1.2.1.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1 -prefs=runtime.tools.arduinoOTA.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arduinoOTA\1.2.1 -prefs=runtime.tools.arduinoOTA-1.2.1.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arduinoOTA\1.2.1 -prefs=runtime.tools.openocd.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\arduino\tools\openocd\0.10.0-arduino7 -prefs=runtime.tools.openocd-0.10.0-arduino7.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\arduino\tools\openocd\0.10.0-arduino7 -prefs=runtime.tools.arm-none-eabi-gcc.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arm-none-eabi-gcc\7-2017q4 -prefs=runtime.tools.arm-none-eabi-gcc-7-2017q4.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arm-none-eabi-gcc\7-2017q4 -prefs=runtime.tools.bossac.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\bossac\1.7.0-arduino3 -prefs=runtime.tools.bossac-1.8.0-48-gb176eee.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\bossac\1.8.0-48-gb176eee -prefs=runtime.tools.CMSIS.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS\5.4.0 -prefs=runtime.tools.CMSIS-5.4.0.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS\5.4.0 -prefs=runtime.tools.bossac-1.7.0-arduino3.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\bossac\1.7.0-arduino3 -verbose F:\MyPortable-PRgs\arduino-1.8.16\portable\sketchbook\USB-Keyboard-Emulator-Demo-001\USB-Keyboard-Emulator-Demo-001.ino
F:\MyPortable-PRgs\arduino-1.8.16\arduino-builder -compile -logger=machine -hardware F:\MyPortable-PRgs\arduino-1.8.16\hardware -hardware F:\MyPortable-PRgs\arduino-1.8.16\portable\packages -hardware F:\MyPortable-PRgs\arduino-1.8.16\portable\sketchbook\hardware -tools F:\MyPortable-PRgs\arduino-1.8.16\tools-builder -tools F:\MyPortable-PRgs\arduino-1.8.16\hardware\tools\avr -tools F:\MyPortable-PRgs\arduino-1.8.16\portable\packages -built-in-libraries F:\MyPortable-PRgs\arduino-1.8.16\libraries -libraries F:\MyPortable-PRgs\arduino-1.8.16\portable\sketchbook\libraries -fqbn=Seeeduino:samd:seeed_XIAO_m0:usbstack=arduino,debug=off -vid-pid=2886_802F -ide-version=10816 -build-path C:\Users\dipl-\AppData\Local\Temp\arduino_build_139330 -warnings=none -build-cache C:\Users\dipl-\AppData\Local\Temp\arduino_cache_332152 -prefs=build.warn_data_percentage=75 -prefs=runtime.tools.CMSIS-Atmel.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1 -prefs=runtime.tools.CMSIS-Atmel-1.2.1.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS-Atmel\1.2.1 -prefs=runtime.tools.arduinoOTA.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arduinoOTA\1.2.1 -prefs=runtime.tools.arduinoOTA-1.2.1.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arduinoOTA\1.2.1 -prefs=runtime.tools.openocd.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\arduino\tools\openocd\0.10.0-arduino7 -prefs=runtime.tools.openocd-0.10.0-arduino7.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\arduino\tools\openocd\0.10.0-arduino7 -prefs=runtime.tools.arm-none-eabi-gcc.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arm-none-eabi-gcc\7-2017q4 -prefs=runtime.tools.arm-none-eabi-gcc-7-2017q4.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\arm-none-eabi-gcc\7-2017q4 -prefs=runtime.tools.bossac.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\bossac\1.7.0-arduino3 -prefs=runtime.tools.bossac-1.8.0-48-gb176eee.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\bossac\1.8.0-48-gb176eee -prefs=runtime.tools.CMSIS.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS\5.4.0 -prefs=runtime.tools.CMSIS-5.4.0.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\CMSIS\5.4.0 -prefs=runtime.tools.bossac-1.7.0-arduino3.path=F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\tools\bossac\1.7.0-arduino3 -verbose F:\MyPortable-PRgs\arduino-1.8.16\portable\sketchbook\USB-Keyboard-Emulator-Demo-001\USB-Keyboard-Emulator-Demo-001.ino
Using board 'seeed_XIAO_m0' from platform in folder: F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\hardware\samd\1.8.1
Using core 'arduino' from platform in folder: F:\MyPortable-PRgs\arduino-1.8.16\portable\packages\Seeeduino\hardware\samd\1.8.1
Detecting libraries used...
"F:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\tools\\arm-none-eabi-gcc\\7-2017q4/bin/arm-none-eabi-g++" -mcpu=cortex-m0plus -mthumb -c -g -Os -w -std=gnu++14 -ffunction-sections -fdata-sections -fno-threadsafe-statics -nostdlib --param max-inline-insns-single=500 -fno-rtti -fno-exceptions "-D__SKETCH_NAME__=\"\"\"USB-Keyboard-Emulator-Demo-001.ino\"\"\"" -w -x c++ -E -CC -DF_CPU=48000000L -DARDUINO=10816 -DARDUINO_SEEED_XIAO_M0 -DARDUINO_ARCH_SAMD -DARDUINO_SAMD_ZERO -D__SAMD21__ -D__SAMD21G18A__ -DARM_MATH_CM0PLUS -DSEEED_XIAO_M0 -DUSB_VID=0x2886 -DUSB_PID=0x802F -DUSBCON -DUSB_CONFIG_POWER=100 "-DUSB_MANUFACTURER=\"Seeed\"" "-DUSB_PRODUCT=\"Seeed XIAO M0\"" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino/TinyUSB" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino/TinyUSB/Adafruit_TinyUSB_ArduinoCore" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src" -DARDUINO_SAMD_ZERO -D__SAMD21__ -D__SAMD21G18A__ -DARM_MATH_CM0PLUS -DSEEED_XIAO_M0 -DUSB_VID=0x2886 -DUSB_PID=0x802F -DUSBCON -DUSB_CONFIG_POWER=100 "-DUSB_MANUFACTURER=\"Seeed\"" "-DUSB_PRODUCT=\"Seeed XIAO M0\"" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino/TinyUSB" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino/TinyUSB/Adafruit_TinyUSB_ArduinoCore" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino/TinyUSB/Adafruit_TinyUSB_ArduinoCore/tinyusb/src" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\tools\\CMSIS\\5.4.0/CMSIS/Core/Include/" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\tools\\CMSIS\\5.4.0/CMSIS/DSP/Include/" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\tools\\CMSIS-Atmel\\1.2.1/CMSIS-Atmel/CMSIS/Device/ATMEL/" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\cores\\arduino" "-IF:\\MyPortable-PRgs\\arduino-1.8.16\\portable\\packages\\Seeeduino\\hardware\\samd\\1.8.1\\variants\\XIAO_m0" "C:\\Users\\dipl-\\AppData\\Local\\Temp\\arduino_build_139330\\sketch\\USB-Keyboard-Emulator-Demo-001.ino.cpp" -o nul
Alternatives for KeyboardUTF8.h: []
ResolveLibrary(KeyboardUTF8.h)
  -> candidates: []or-Demo-001:1:10: fatal error: KeyboardUTF8.h: No such file or directory

 #include <KeyboardUTF8.h>
          ^~~~~~~~~~~~~~~~
compilation terminated.
exit status 1
KeyboardUTF8.h: No such file or directory

The installation-paths look like this


From re-checking the paths I would say it is correct and should be found

What is still wrong that it does not compile?

best regards Stefan

It's not clear why it didn't find the library. Did you use Sketch -> Include Library -> Add .ZIP Library...? or did you copy the library folder in manually? I think opening Tools -> Manage Libraries... will cause it to re-index your library files.

I tried both. In both cases no success.

Does this mean the Arduino-IDE builds some kind of internal index that needs to be called again after installing a ZIP-library?

I never needed to do this with other ZIP-libraries.

I started a separated thread about this problem and describe there some steps what I tried. Still trying some more things...

best regards Stefan

@johnwasser : I stay with my own german keyboard-library.

I tried your testcode just modified to send all characters on the keyboard.
And I added some security against locking yourself out through to much keypresses

#include <KeyboardUTF8.h>
#include <src/Keyboard_GR.h>

// Keyboard_GR Keyboard_german;
boolean sendKeysOnlyOnce = false;

void setup(){
  Serial.begin(115200);
  Serial.println("setup-Start");
  //Keyboard_GR.begin();
  //Keyboard_GR.println("<>|µ,;.:-_#+*~€²³{[]}ß?");
}

void loop() {
  Serial.println("waiting 20 seconds before key-send");
  delay(20000);
  if (!sendKeysOnlyOnce) {
    sendKeysOnlyOnce = true;
    Serial.println("send to USB:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZäöüÄÖÜ");
    Keyboard_GR.println("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZäöüÄÖÜ");
    Serial.println("send to USB:<>|µ,;.:-_#+*~€²³{[]}ß?");
    Keyboard_GR.println("<>|µ,;.:-_#+*~€²³{[]}ß?");
  }
  }

This caused my computer to react strange.
Instead of typing letters activating the save-dialog etc.

best regards Stefan

Move the KeyboardUTF8.h and KeyboardUTF8.cpp files into the src directory.
Change line #2 in the example:

#include <KeyboardUTF8.h>
#include <Keyboard_GR.h> //<src/Keyboard_GR.h>

// Keyboard_GR Keyboard_german;

void setup()
{
  Keyboard_GR.println("This is a test.");
}

void loop() {}

Please add code tags around the list of available layouts in README.md. :slight_smile: