Keyboard-Scancodes for german keyboards

I'm working on a project where I want to use an ESP32 as a bluetooth BLE keyboard-emulator for a german keyboard-layout.

On a german keyboard some keys have a different position on the keyboard.
This means thoses keys have a different scancode than on a US-keyboard-layout.

I have found a document that describes the differencies for most keys

But the following keys are missing in this list:

[ { ] } \ | ~

I started this question in the german sub-forum here

But the answers are pretty small. Maybe due to that I posted an approach how I can solve the problem myself.
So now I ask in the international user-forum.

I hope the moderators don't see this as "cross-posting" but as well documented change of language.

Now I'm stuck with the letters like written above.
Trying to do a manual "brute force" analysis is difficult as this is dealing with keyboard-presses where sending every possible value will cause unwanted things like

  • call up help-function keypress F1
  • start printer-dialog Ctrl-p
    etc.

So if anybody knows of a link that has a really complete list of the german scan-codes I would be very happy and thankful.

best regards Stefan

Launch xev from a terminal (assuming you are running *NIX and X). As you type the keys, you will get a bunch of info that you can take note of.

Besides the Shift key you need support for a second modifier key, to distinguish more than two characters for a single key: E.g. a german keyboard can create 'q' 'Q' and '@' with the same keystroke, depending on shift / Alt Gr modifier.

hm well I'm not very familiar with unix/linux terms
Do you mean "xev" is a unix/linux-tool?
Anyway for this purpose installing some kind of linux on my windows-computer is an option I would only choose if this would be the last option to save my life.
best regards Stefan

On the Mac there is an helper tool that let you see the keyboard key mapping, when you press modifiers, so it's just a matter of looking at the German keyboard layout and what it corresponds to in the American keyboard layout

For example { from a German keyboard would be obtained by pressing alt7 on an American keyboard so you could possibly mimic this by sending those two key press and in the receiving OS as the keyboard is configured as German, it would be seen as { ?

Yes and I managed to find the keymodifier for the "at"-symbol "@"
in case of the "@" it is q with modifiers Ctrl-Alt.
hm ... --- ... good idea maybe I should just try 7 Ctrl-Alt for the "{" etc.
:smiley: busy with testing this idea....

Yes, and a quite standard one. If you don't have some UNIX or Linux box around, you can try with a live Linux distro (so you don't need to change anything on your machine). Knoppix is one of them (and it's German, too), but there are many others: you may want to take a look here.

Sorry, this was written for the German Part.

Eventuell noch ein Stückchen:

Wenn ich in Win10 Shift+Alt für ca. 3 Sekunden drücke, wird auf das US-Layout umgeschaltet. Oben deutsch, unten US:

ü Ü + * ö Ö ä Ä # ' , ; . : - _
[ { ] } ; : ' " \ | , < . > / ?

Hi agmue,

the codes that must be sended over BLE are different.

The keyboard-emulation uses an array of a struct that contains the keycodes and the modifiers

typedef struct {
	unsigned char usage;  // keycode
	unsigned char modifier;
} KEYMAP;

In the meantime I found the BLE-codes and key-modifiers for all characters with ASCII-Code-values below 127.

for a string that only contains characters below ASCII-code 127
For those characters it is sufficient to do a typecasting

    uint8_t val = (uint8_t)text[i];

The typecast delivers the ASCII-code.
And the ASCII-code equals to the index-number in the array of struct KEYMAP.
This way works a lookup in an array of struct with all the keycodes and the (key)modifiers.

#define KEYMAP_SIZE (123)
const KEYMAP keymap[KEYMAP_SIZE] = {
   {0x00, 0 }, /* 0  */
// ...   shortened for better overview
{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   '  */
//....

Now the characters "äöü ÄÖÜ ² ³ €" are represented by multibyte values.
This means a simple typecast to uint8_t does not work.

analysing the values beeing above or below 126 and then execute different code for retrieving the correct keycodes and modifiers is easy

But I have no idea yet how to determine the BLE-keycodes for these characters "äöü ÄÖÜ ² ³ €",
best regards Stefan

I don't know how much it will help but here are the USB codes for the keys on the main part of the 84-key keyboard. Non-US keyboards will often have some additional keys near the right end of the keyboard.

[35][1E][1F][20][21][22][23][24][25][26][27][2D][2E][ 2A ]
[2B ][14][1A][08][15][17][1C][A0][0C][12][13][2f][30][31]
[39 ] [04][16][07][09][0A][0B][0D][0E][0F][33][34][  28  ]
[Shift ][1D][1B][06][19][05][11][10][36][37][38][Shift ]
                    [   2C   ]

[ `][ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][ 0][ -][ =][Del]
[Tab ][ q][ w][ e][ r][ t][ y][ u][ I][ o][ p][ {][ }][ \]
[Caps] [ a][ s][ d][ f][ g][ h][ j][ k][ l][ ;][ '][ Ret]
[Shift ][ z][ x][ c][ v][ b][ n][ m][ ,][ .][ /][Shift ]
                    [   Spacebar   ]

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

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.