Keyboard-Scancodes for german keyboards

Hi everybody,

finally "the coin dropped into the box" :wink:

all characters that are accessed through pressing AltGr on a german keyboard have the scancode pure Key + modifier Ctrl-Alt

I did a half automated brute-force analysis of the keycodes.
This way I found the keycodes for äöü.
Walking through all keycodes in a loop of course I came across some keys like window-key, page-up etc. which are executed as always.
That was the reason to use a half-automtated function which waits for a button-press after each send-keystroke.

So final question where to post the working demo-code?
Just here? or is there a better suited place for it?

The complete project is a device that automates a login that requires

  • typing in a looong emailadress as login-name
  • a pretty loong password
  • and a OTP-password based on a secret and actual time

So I have all parts working now.
Next step is to add code for configuration of the login/password/secret over a serial connection or maybe a webinterface

here is the demo-code with deleted password and secret

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVar,myInterval);
// myVar can be any variable or expression that is defined in scope
// myInterval is the time-interval which must pass by before the next
// print is executed

#define US_KEYBOARD 1

#include <Arduino.h>
#include "BLEDevice.h"
#include "BLEHIDDevice.h"
#include "HIDTypes.h"
#include "HIDKeyboardTypes.h"

char KeyCode = 0;

// Change the below values if desired
#define BUTTON_PIN 0 //2
#define Button_D2 15

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 = 20;

#define MESSAGE "Hello from ESP32\n"
#define DEVICE_NAME "ESP32 Keyboard"

// Forward declarations
void bluetoothTask(void*);
void typeText(const char* text);

bool isBleConnected = false;

#include <WiFi.h>
#include <NTPClient.h>
#include <TOTP.h>

#include <SafeString.h>
createSafeString(myDemo_SS, 256);
createSafeString(KeyBoardText, 256);

// change the following settings according to your WiFi network
char ssid[] = "FRITZ!Box 7490";
char password[] = "";


uint8_t hmacKey[] = {}

TOTP totp = TOTP(hmacKey, 16);
char code[17];


WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);

String totpCode = String("");

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


boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = 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 = 2;


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() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  // configure pin for button
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(Button_D2, INPUT_PULLUP);
  
  // start Bluetooth task
  xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL);

  // connect to the WiFi network
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Establishing connection to WiFi...");
  }
  Serial.print("Connected to WiFi with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  // start the NTP client
  timeClient.begin();
  Serial.println("NTP client started");
  Serial.println();  
}

unsigned long myEpocTime;


// Message (report) sent when a key is pressed or released
struct KeyMessage_t {
  uint8_t modifiers;       // bitmask: CTRL = 1, SHIFT = 2, ALT = 4
  uint8_t reserved;        // must be 0
  uint8_t pressedKeys[6];  // up to six concurrently pressed keys
};


// Message (report) received when an LED's state changed
struct OutputReport {
  uint8_t leds;            // bitmask: num lock = 1, caps lock = 2, scroll lock = 4, compose = 8, kana = 16
};


// The report map describes the HID device (a keyboard in this case) and
// the messages (reports in HID terms) sent and received.
static const uint8_t REPORT_MAP[] = {
  USAGE_PAGE(1),      0x01,       // Generic Desktop Controls
  USAGE(1),           0x06,       // Keyboard
  COLLECTION(1),      0x01,       // Application
  REPORT_ID(1),       0x01,       //   Report ID (1)
  USAGE_PAGE(1),      0x07,       //   Keyboard/Keypad
  USAGE_MINIMUM(1),   0xE0,       //   Keyboard Left Control
  USAGE_MAXIMUM(1),   0xE7,       //   Keyboard Right Control
  LOGICAL_MINIMUM(1), 0x00,       //   Each bit is either 0 or 1
  LOGICAL_MAXIMUM(1), 0x01,
  REPORT_COUNT(1),    0x08,       //   8 bits for the modifier keys
  REPORT_SIZE(1),     0x01,
  HIDINPUT(1),        0x02,       //   Data, Var, Abs
  REPORT_COUNT(1),    0x01,       //   1 byte (unused)
  REPORT_SIZE(1),     0x08,
  HIDINPUT(1),        0x01,       //   Const, Array, Abs
  REPORT_COUNT(1),    0x06,       //   6 bytes (for up to 6 concurrently pressed keys)
  REPORT_SIZE(1),     0x08,
  LOGICAL_MINIMUM(1), 0x00,
  LOGICAL_MAXIMUM(1), 0xE7,       //   full range 0xE7 //  101 keys 0x65 
  USAGE_MINIMUM(1),   0x00,
  USAGE_MAXIMUM(1),   0xE7,
  HIDINPUT(1),        0x00,       //   Data, Array, Abs
  REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
  REPORT_SIZE(1),     0x01,
  USAGE_PAGE(1),      0x08,       //   LEDs
  USAGE_MINIMUM(1),   0x01,       //   Num Lock
  USAGE_MAXIMUM(1),   0x05,       //   Kana
  LOGICAL_MINIMUM(1), 0x00,
  LOGICAL_MAXIMUM(1), 0x01,
  HIDOUTPUT(1),       0x02,       //   Data, Var, Abs
  REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
  REPORT_SIZE(1),     0x03,
  HIDOUTPUT(1),       0x01,       //   Const, Array, Abs
  END_COLLECTION(0)               // End application collection
};


BLEHIDDevice* hid;
BLECharacteristic* input;
BLECharacteristic* output;

const KeyMessage_t NO_KEY_PRESSED = { };

//   Callbacks related to BLE connection
class BleKeyboardCallbacks : public BLEServerCallbacks {

    void onConnect(BLEServer* server) {
      isBleConnected = true;

      // Allow notifications for characteristics
      BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
      cccDesc->setNotifications(true);

      Serial.println("Client has connected");
    }

    void onDisconnect(BLEServer* server) {
      isBleConnected = false;

      // Disallow notifications for characteristics
      BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902));
      cccDesc->setNotifications(false);

      Serial.println("Client has disconnected");
    }
};


/*
   Called when the client (computer, smart phone) wants to turn on or off
   the LEDs in the keyboard.

   bit 0 - NUM LOCK
   bit 1 - CAPS LOCK
   bit 2 - SCROLL LOCK
*/
class OutputCallbacks : public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic* characteristic) {
      OutputReport* report = (OutputReport*) characteristic->getData();
      Serial.print("LED state: ");
      Serial.print((int) report->leds);
      Serial.println();
    }
};


void bluetoothTask(void*) {

  // initialize the device
  BLEDevice::init(DEVICE_NAME);
  BLEServer* server = BLEDevice::createServer();
  server->setCallbacks(new BleKeyboardCallbacks());

  // create an HID device
  hid = new BLEHIDDevice(server);
  input = hid->inputReport(1); // report ID
  output = hid->outputReport(1); // report ID
  output->setCallbacks(new OutputCallbacks());

  // set manufacturer name
  hid->manufacturer()->setValue("Maker Community");
  // set USB vendor and product ID
  hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
  // information about HID device: device is not localized, device can be connected
  hid->hidInfo(0x00, 0x02);

  // Security: device requires bonding
  BLESecurity* security = new BLESecurity();
  security->setAuthenticationMode(ESP_LE_AUTH_BOND);

  // set report map
  hid->reportMap((uint8_t*)REPORT_MAP, sizeof(REPORT_MAP));
  hid->startServices();

  // set battery level to 100%
  hid->setBatteryLevel(100);

  // advertise the services
  BLEAdvertising* advertising = server->getAdvertising();
  advertising->setAppearance(HID_KEYBOARD);
  advertising->addServiceUUID(hid->hidService()->getUUID());
  advertising->addServiceUUID(hid->deviceInfo()->getUUID());
  advertising->addServiceUUID(hid->batteryService()->getUUID());
  advertising->start();

  Serial.println("BLE ready");
  delay(portMAX_DELAY);
};

void SendReturn() {
  PureInputReport(88,0);
}

void PureInputReport(byte p_KeyCode, byte p_modifier) {
  KeyMessage_t report = { // create input report
    .modifiers = p_modifier, 
    .reserved = 0,
    .pressedKeys = {
      p_KeyCode,
      0, 0, 0, 0, 0
    }
  };
  // send the input report
  input->setValue((uint8_t*)&report, sizeof(report));
  input->notify();

  delay(5);

  // release all keys between two characters; otherwise two identical
  // consecutive characters are treated as just one key press
  input->setValue((uint8_t*)&NO_KEY_PRESSED, sizeof(NO_KEY_PRESSED));
  input->notify();

  delay(5);  
}


void typeText(const char* text) {
  KeyMessage_t myKeyMessage;
  myKeyMessage.reserved = 0;
  uint8_t val; 
  uint8_t SecondByte; 
  uint8_t ThirdByte; 

  int len = strlen(text);
  for (int i = 0; i < len; i++) {

    for (int i = 0; i < 6; i++) {
      myKeyMessage.pressedKeys[i] = 0;
    }
    // translate character to key combination
    val = (uint8_t)text[i];
    
    if (val > KEYMAP_SIZE) { // character has a multibyte representation
      switch (val) {
        case aeoeue_1stByte: // if it's an äöü
          i++;
          SecondByte = text[i];
          
          if (SecondByte == ae_2ndByte) { // if it's an ä
            myKeyMessage.pressedKeys[0] = 52;
            myKeyMessage.modifiers   = 0;
            break;
          }

          if (SecondByte == oe_2ndByte) { // if it's an ü
            myKeyMessage.pressedKeys[0] = 51;
            myKeyMessage.modifiers   = 0;
            break;
          }

          if (SecondByte == ue_2ndByte) { // if it's an ü
            myKeyMessage.pressedKeys[0] = 47;
            myKeyMessage.modifiers   = 0;
            break;
          }

          if (SecondByte == Ae_2ndByte) { // if it's an ä
            myKeyMessage.pressedKeys[0] = 52;
            myKeyMessage.modifiers   = Shift;
            break;
          }

          if (SecondByte == Oe_2ndByte) { // if it's an ü
            myKeyMessage.pressedKeys[0] = 51;
            myKeyMessage.modifiers   = Shift;
            break;
          }

          if (SecondByte == Ue_2ndByte) { // if it's an ü
            myKeyMessage.pressedKeys[0] = 47;
            myKeyMessage.modifiers   = Shift;
            break;
          }

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

          if (SecondByte == mue_2ndByte) { // if it's an µ
            myKeyMessage.pressedKeys[0] = 16;
            myKeyMessage.modifiers   = CtrlAlt;
            break;
          }
          
          if (SecondByte == mue2_2ndByte) { // if it's an ²
            myKeyMessage.pressedKeys[0] = 31;
            myKeyMessage.modifiers   = CtrlAlt;
            break;
          }

          if (SecondByte == mue3_2ndByte) { // if it's an ³
            myKeyMessage.pressedKeys[0] = 32;
            myKeyMessage.modifiers   = CtrlAlt;
            break;
          }

          if (SecondByte == degree_2ndByte) { // if it's an °
            myKeyMessage.pressedKeys[0] = 53;
            myKeyMessage.modifiers   = Shift;
            break;
          }

          if (SecondByte == paragraph_2ndByte) { // if it's an §
            myKeyMessage.pressedKeys[0] = 32;
            myKeyMessage.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) {
              myKeyMessage.pressedKeys[0] = 8;
              myKeyMessage.modifiers   = CtrlAlt;
              break;              
            }
          }
        default:
            myKeyMessage.pressedKeys[0] = 0;
            myKeyMessage.modifiers      = 0;
            break;            
      }
      
      Serial.println("val > KEYMAP_SIZE special treatment");
    }
    else { // character has a single-byte representation
      KEYMAP myKeyAndMod = keymap[val];
      myKeyMessage.modifiers = myKeyAndMod.modifier;
      myKeyMessage.pressedKeys[0] = myKeyAndMod.usage;
    }  
    
    // send the input report
    input->setValue((uint8_t*)&myKeyMessage, sizeof(myKeyMessage));
    input->notify();

    delay(SendDelay);

    // release all keys between two characters; otherwise two identical
    // consecutive characters are treated as just one key press
    input->setValue((uint8_t*)&NO_KEY_PRESSED, sizeof(NO_KEY_PRESSED));
    input->notify();

    delay(SendDelay);
  }
}

#define KEY_CODE_PAGE_UP 0x4b
#define KEY_CODE_PAGE_DOWN 0x4e


void loop() {
  BlinkHeartBeatLED(OnBoard_LED,500);
  
  if (isBleConnected && digitalRead(BUTTON_PIN) == LOW) {
    // button has been pressed: type message    
    Serial.println(MESSAGE);
    typeText(MESSAGE);
  }

  if (isBleConnected && digitalRead(Button_D2) == LOW) { 
    delay(200);
    KeyBoardText = "12345678901234567890";
    dbg("btn-press",KeyBoardText);
    //Serial.println(KeyBoardText);
    typeText(KeyBoardText.c_str());
    SendReturn(); 
    
    delay(500);
    KeyBoardText = "abcdefghijklmnopqrstuvwxyzäöü";
    dbg("btn-press",KeyBoardText);
    //Serial.println(KeyBoardText);
    typeText(KeyBoardText.c_str());
    //SendReturn(); 
    delay(500);
    KeyBoardText = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ";
    Serial.println(KeyBoardText);
    typeText(KeyBoardText.c_str());
    //SendReturn(); 
    delay(500);
            
    KeyBoardText = "1234567890!§$%&/()=?^°<>|@µ²³#',;.:-_߀";
    Serial.println(KeyBoardText);
    typeText(KeyBoardText.c_str());
    SendReturn(); 
    delay(500);
  }  

  /*
  // update the time 
  timeClient.update();

  // generate the TOTP code and, if different from the previous one, print to screen
  myEpocTime = timeClient.getEpochTime();
  
  String newCode = String(totp.getCode(myEpocTime));
  if(totpCode!= newCode && false) {
    totpCode = String(newCode);
    Serial.print("TOTP code: ");
    Serial.println(newCode);
  }


  if ( TimePeriodIsOver(MyTestTimer,5000) && false) {
    dbg("1:",myEpocTime);
    dbg("2:",timeClient.getFormattedTime() );
    dbg("3:",newCode);
  }    
  */
}

I have tested this democode with all almost all characters available on a german keyboard
abcdefghijklmnopqrstuvwxyzäöüABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ1234567890!§$%&/()=?^°<>|@µ²³#',;.:-_߀

I have modified the array of struct in the HIDKeyboardTypes.h-library
as I don't use any US-keyboard

#ifdef US_KEYBOARD
/* German keyboard (as HID standard) */
#define KEYMAP_SIZE (127)
const KEYMAP keymap[KEYMAP_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  */
   {0x00, 0 }, /* 10  */
   {0x00, 0 }, /* 11  */
   {0x00, 0 }, /* 12  */
   {0x00, 0 }, /* 13  */
   {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   !  */
   {0x0 , 0 }, /* 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 */
   {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   ~ */
};

best regards Stefan