Joy_of_Arcada: add support for JSON config file

This was a huge ordeal and I deserve a Nobel Prize or something.
This commit is contained in:
Phillip Burgess 2019-06-14 11:24:28 -07:00
parent 870ea7c8f2
commit 2aa4489d67
2 changed files with 346 additions and 80 deletions

View file

@ -1,23 +1,66 @@
// JOY: Adafruit PyGamer or PyBadge as a friendly USB HID game controller.
// Requires Arcada and Audio libraries.
/*
JOY: Adafruit PyGamer or PyBadge as a friendly USB HID game controller.
Requires Arcada, ArduinoJSON and Audio libraries.
Button-to-key mapping can be configured in the file joy.cfg in the root
folder of the flash filesystem, else defaults will be used. This is a
JSON-formatted text file and the syntax is VERY particular. Keywords
(corresponding to button names) MUST be lowercase, in quotes, and one
of the recognized strings ("a", "b", "start", "select", "up", "down",
"left" or "right"). Values (corresponding to HID keyboard codes) are
not case-sensitive, but must be one of the recognized values in keys.h.
The key:value list MUST be { enclosed in braces } and the last key:value
must NOT have a trailing comma. There are NO COMMENTS in the file.
Picky, yes, but less bother than recompiling all this code to change
the key mapping (though you can still do that if you want to change the
default keys when a config file is not found or has syntax trouble).
Here's an example of a valid joy.cfg file with the default key mapping:
{
"a": "Z",
"b": "X",
"start": "1",
"select": "5",
"up": "UP_ARROW",
"down": "DOWN_ARROW",
"left": "LEFT_ARROW",
"right": "RIGHT_ARROW"
}
*/
#if !defined(USE_TINYUSB)
#error("Please select TinyUSB from the Tools->USB Stack menu!")
#endif
#include <Adafruit_Arcada.h>
#include <Audio.h>
#include <Keyboard.h>
#include "graphics.h" // Face bitmaps are here
#include "sound.h" // "Pew" sound is here
#include "keys.h" // Key name-to-value table is here
//#include "Adafruit_TinyUSB.h"
// Keys corresponding to the various controller buttons.
// If special keycodes are required (shift, arrows, etc.), docs are here:
// https://www.arduino.cc/en/Reference/KeyboardModifiers
#define KEY_A 'z'
#define KEY_B 'x'
#define KEY_START '1'
#define KEY_SELECT '5'
#define KEY_UP KEY_UP_ARROW
#define KEY_DOWN KEY_DOWN_ARROW
#define KEY_LEFT KEY_LEFT_ARROW
#define KEY_RIGHT KEY_RIGHT_ARROW
#define JOY_CONFIG_FILE "/joy.cfg"
// These are the indices of each element in the keyCode[] array below,
// so we're not using separate variables for every button.
enum ButtonIndex { BUTTON_A = 0, BUTTON_B, BUTTON_START, BUTTON_SELECT, BUTTON_UP, BUTTON_DOWN, BUTTON_LEFT, BUTTON_RIGHT, NUM_BUTTONS };
// These are default key codes for each of the buttons on the PyBadge / PyGamer.
// Key codes are simply ASCII chars, or one of the values defined in Keyboard.h.
// We'll try loading replacements from a configuration file, else use these fallbacks.
// Order of items must match the ButtonIndex enum above.
uint8_t keyCode[NUM_BUTTONS] = { HID_KEY_Z, HID_KEY_X, HID_KEY_1, HID_KEY_5, HID_KEY_ARROW_UP, HID_KEY_ARROW_DOWN, HID_KEY_ARROW_LEFT, HID_KEY_ARROW_RIGHT };
// Given a key name as a string (e.g. "LEFT_CONTROL"), return the
// corresponding key code (an 8-bit value) from the keys[] array.
uint8_t lookupKey(char *name) {
for(int i=0; i<NUM_KEYS; i++) {
if(!strcasecmp(name, key[i].name)) return key[i].code;
}
return 0; // Not found
}
// MASK_BUTTONS is used to isolate button inputs from direction inputs
// on PyGamer. MASK_PEW is to isolate just the A & B buttons as triggers
@ -35,25 +78,121 @@ uint32_t blinkStartTime, blinkDuration;
uint8_t mouthState = 0, priorMouthState = 0, mouthCounter = 0;
uint32_t stickmask = 0;
Adafruit_USBD_HID usb_hid;
uint8_t const desc_hid_report[] = { TUD_HID_REPORT_DESC_KEYBOARD() };
uint8_t hidcode[6] = { 0,0,0,0,0,0 };
AudioPlayMemory sound;
AudioOutputAnalogStereo audioOut;
AudioConnection c0(sound, 0, audioOut, 0);
void setup() {
Serial.begin(9600);
//while(!Serial); // Wait for Serial Console before continuing
uint32_t keyBits = 0; // Bitmask of currently-pressed keys
#define press(x) { keyBits |= (1 << x); }
#define release(x) { keyBits &= ~(1 << x); }
if(!arcada.begin()) {
Serial.println(F("Arcada failed to init"));
// This function converts keyBits value to HID button presses.
void sendKeys(void) {
static bool prevPressFlag = 0;
if(usb_hid.ready()) {
uint8_t count = 0;
bool pressFlag = 0;
for(int i=0; i<NUM_BUTTONS; i++) { // For each button...
if(keyBits & (1 << i)) { // Pressed?
pressFlag = 1; // At least one key is pressed
hidcode[count++] = keyCode[i]; // Add to HID codes
if(count >= 6) { // 6 codes at a time max
usb_hid.keyboardReport(0, 0, hidcode); // Send what we have
delay(2);
memset(hidcode, 0, sizeof(hidcode)); // Clear hidcode list
count = 0; // Reset hidcode counter
}
}
}
if(count) { // Any remaining hiscodes to send?
usb_hid.keyboardReport(0, 0, hidcode); // Do it nao!
delay(2);
memset(hidcode, 0, sizeof(hidcode));
count = 0;
}
// If no buttons are pressed, but there were previously buttons
// pressed on the last call, issue a suitable HID event...
if(prevPressFlag & !pressFlag) {
usb_hid.keyboardRelease(0);
}
prevPressFlag = pressFlag;
}
}
// SETUP FUNCTION - RUNS ONCE AT STARTUP ***********************************
void setup() {
// Some of these libraries are SUPER PERSNICKETY about the sequence
// in which they are initialized. HID MUST be initialized before
// Serial, which must be initialized before the display.
int status = arcada.begin(); // Save status for Serial print later
// HID (keyboard) initialization
usb_hid.setPollInterval(2);
usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
usb_hid.begin();
// USB filesystem initialization
arcada.filesysBeginMSD();
// Then Serial init, MUST happen after the filesystem init
Serial.begin(9600);
// while(!Serial);
// Now that Serial's initialized, can throw out an error if needed
if(!status) {
Serial.println("Arcada failed to init");
for(;;);
}
// Display initialization. This is all part of the persnickety sequence!
arcada.displayBegin();
arcada.setBacklight(255);
arcada.fillScreen(ARCADA_BLACK);
Keyboard.begin();
// Audio initialization
AudioMemory(10);
arcada.enableSpeaker(true);
arcada.setVolume(255);
if(arcada.filesysBegin()) {
// Valid filesystem found. Load configuration file (if present).
File file = arcada.open(JOY_CONFIG_FILE, O_READ);
if(file) {
StaticJsonDocument<512> doc;
char buf[40];
if(DeserializationError error = deserializeJson(doc, file)) {
arcada.warnBox("JOY.CFG syntax invalid, using default configuration.");
} else {
// Keywords in the JSON file corresponding to each button input.
// Order of these arrays must follow the ButtonIndex enum (but can be any order in file).
static const char *buttonName[] = { "a", "b", "start", "select", "up" , "down" , "left" , "right" },
*buttonDefault[] = { "z", "x", "1" , "5" , "UP_ARROW", "DOWN_ARROW", "LEFT_ARROW", "RIGHT_ARROW" };
// Look up each button in the JSON doc, assign default key value if not found.
for(int i=0; i<NUM_BUTTONS; i++) {
strlcpy(buf, doc[buttonName[i]] | buttonDefault[i], sizeof(buf));
keyCode[i] = lookupKey(buf);
}
}
file.close();
} else {
arcada.warnBox("JOY.CFG file not found, using default configuration.");
}
} else {
arcada.warnBox("No filesystem found! Load CircuitPython to init filesystem. Using default configuration.");
}
// At start, draw the entire blank/neutral face centered on screen
arcada.fillScreen(ARCADA_BLACK); // Erase any warnBoxes first
arcada.drawRGBBitmap(
(arcada.width() - FACE_WIDTH ) / 2,
(arcada.height() - FACE_HEIGHT) / 2,
@ -75,10 +214,10 @@ void setup() {
// Initialize button state
arcada.readButtons();
uint32_t b = arcada.justPressedButtons();
if(b & ARCADA_BUTTONMASK_A) Keyboard.press(KEY_A);
if(b & ARCADA_BUTTONMASK_B) Keyboard.press(KEY_B);
if(b & ARCADA_BUTTONMASK_START) Keyboard.press(KEY_START);
if(b & ARCADA_BUTTONMASK_SELECT) Keyboard.press(KEY_SELECT);
if(b & ARCADA_BUTTONMASK_A) press(BUTTON_A);
if(b & ARCADA_BUTTONMASK_B) press(BUTTON_B);
if(b & ARCADA_BUTTONMASK_START) press(BUTTON_START);
if(b & ARCADA_BUTTONMASK_SELECT) press(BUTTON_SELECT);
#if defined(ARCADA_JOYSTICK_X) && defined(ARCADA_JOYSTICK_Y)
// Initialize joystick state. Although the Arcada lib has stuff for
@ -86,33 +225,37 @@ void setup() {
// hysteresis built in, so I'm leaving it here for now, though bulkier.
int pos = arcada.readJoystickX() + 512;
if(pos > (1023 * 4 / 5)) {
Keyboard.press(KEY_RIGHT);
press(BUTTON_RIGHT);
xState = 1;
} else if(pos < (1023 / 5)) {
Keyboard.press(KEY_LEFT);
press(BUTTON_LEFT);
xState = -1;
} else {
xState = 0;
}
pos = arcada.readJoystickY() + 512;
if(pos > (1023 * 4 / 5)) {
Keyboard.press(KEY_DOWN);
press(BUTTON_DOWN);
yState = 1;
} else if(pos < (1023 / 5)) {
Keyboard.press(KEY_UP);
press(BUTTON_UP);
yState = -1;
} else {
yState = 0;
}
#else
if(b & ARCADA_BUTTONMASK_RIGHT) Keyboard.press(KEY_RIGHT);
if(b & ARCADA_BUTTONMASK_LEFT) Keyboard.press(KEY_LEFT);
if(b & ARCADA_BUTTONMASK_DOWN) Keyboard.press(KEY_DOWN);
if(b & ARCADA_BUTTONMASK_UP) Keyboard.press(KEY_UP);
if(b & ARCADA_BUTTONMASK_RIGHT) press(BUTTON_RIGHT);
if(b & ARCADA_BUTTONMASK_LEFT) press(BUTTON_LEFT);
if(b & ARCADA_BUTTONMASK_DOWN) press(BUTTON_DOWN);
if(b & ARCADA_BUTTONMASK_UP) press(BUTTON_UP);
stickmask = b & ~MASK_BUTTONS;
#endif
sendKeys(); // Issue initial HID key state
}
// LOOP FUNCTION - RUNS REPEATEDLY, ONCE PER FRAME *************************
void loop() {
int dx, dy, upperLidRows=0, lowerLidRows=0, openRows=EYES_HEIGHT;
float a;
@ -122,10 +265,10 @@ void loop() {
arcada.readButtons();
b = arcada.justPressedButtons();
if(b & ARCADA_BUTTONMASK_A) Keyboard.press(KEY_A);
if(b & ARCADA_BUTTONMASK_B) Keyboard.press(KEY_B);
if(b & ARCADA_BUTTONMASK_START) Keyboard.press(KEY_START);
if(b & ARCADA_BUTTONMASK_SELECT) Keyboard.press(KEY_SELECT);
if(b & ARCADA_BUTTONMASK_A) press(BUTTON_A);
if(b & ARCADA_BUTTONMASK_B) press(BUTTON_B);
if(b & ARCADA_BUTTONMASK_START) press(BUTTON_START);
if(b & ARCADA_BUTTONMASK_SELECT) press(BUTTON_SELECT);
// If one of the pewing buttons was pressed, and mouth is not currently
// in the "py" position (so, either idle or "oo"ing), there's a random
// chance (1/8) of triggering a new "pew!" sound & animation...
@ -133,64 +276,64 @@ void loop() {
mouthState = 1; // Set flag, actual pew starts in the mouth code later
}
b = arcada.justReleasedButtons();
if(b & ARCADA_BUTTONMASK_A) Keyboard.release(KEY_A);
if(b & ARCADA_BUTTONMASK_B) Keyboard.release(KEY_B);
if(b & ARCADA_BUTTONMASK_START) Keyboard.release(KEY_START);
if(b & ARCADA_BUTTONMASK_SELECT) Keyboard.release(KEY_SELECT);
if(b & ARCADA_BUTTONMASK_A) release(BUTTON_A);
if(b & ARCADA_BUTTONMASK_B) release(BUTTON_B);
if(b & ARCADA_BUTTONMASK_START) release(BUTTON_START);
if(b & ARCADA_BUTTONMASK_SELECT) release(BUTTON_SELECT);
#if defined(ARCADA_JOYSTICK_X) && defined(ARCADA_JOYSTICK_Y)
// Analog joystick input with fancy hysteresis
dx = arcada.readJoystickX(), // Joystick position relative to center
dy = arcada.readJoystickY(); // (+/- 512)
dx = arcada.readJoystickX(), // Joystick position relative to center
dy = arcada.readJoystickY(); // (+/- 512)
// Handle joystick X axis
int pos = dx + 512;
if(xState == 1) { // Stick to right last we checked?
if(pos < (1023 * 3 / 5)) { // Moved left beyond hysteresis threshold?
Keyboard.release(KEY_RIGHT); // Release right arrow key
xState = 0; // and set state to neutral center zone
if(xState == 1) { // Stick to right last we checked?
if(pos < (1023 * 3 / 5)) { // Moved left beyond hysteresis threshold?
release(BUTTON_RIGHT); // Release right arrow key
xState = 0; // and set state to neutral center zone
}
} else if(xState == -1) { // Stick to left last we checked?
if(pos > (1023 * 2 / 5)) { // Moved right beyond hysteresis threshold?
Keyboard.release(KEY_LEFT); // Release left arrow key
xState = 0; // and set state to neutral center zone
} else if(xState == -1) { // Stick to left last we checked?
if(pos > (1023 * 2 / 5)) { // Moved right beyond hysteresis threshold?
release(BUTTON_LEFT); // Release left arrow key
xState = 0; // and set state to neutral center zone
}
}
// This is intentionally NOT an 'else' -- state CAN change twice here!
// First change releases left/right keys, second change presses new left/right
if(!xState) { // Stick X previously in neutral center zone?
if(pos > (1023 * 4 / 5)) { // Moved right?
Keyboard.press(KEY_RIGHT); // Press right arrow key
xState = 1; // and set state to right
} else if(pos < (1023 / 5)) { // Else moved left?
Keyboard.press(KEY_LEFT); // Press left arrow key
xState = -1; // and set state to left
if(!xState) { // Stick X previously in neutral center zone?
if(pos > (1023 * 4 / 5)) { // Moved right?
press(BUTTON_RIGHT); // Press right arrow key
xState = 1; // and set state to right
} else if(pos < (1023 / 5)) { // Else moved left?
press(BUTTON_LEFT); // Press left arrow key
xState = -1; // and set state to left
}
}
// Handle joystick Y axis
pos = dy + 512;
if(yState == 1) { // Stick down last we checked?
if(pos < (1023 * 3 / 5)) { // Moved up beyond hysteresis threshold?
Keyboard.release(KEY_DOWN); // Release down key
yState = 0; // and set state to neutral center zone
if(yState == 1) { // Stick down last we checked?
if(pos < (1023 * 3 / 5)) { // Moved up beyond hysteresis threshold?
release(BUTTON_DOWN); // Release down key
yState = 0; // and set state to neutral center zone
}
} else if(yState == -1) { // Stick up last we checked?
if(pos > (1023 * 2 / 5)) { // Moved down beyond hysteresis threshold?
Keyboard.release(KEY_UP); // Release up key
yState = 0; // and set state to neutral center zone
} else if(yState == -1) { // Stick up last we checked?
if(pos > (1023 * 2 / 5)) { // Moved down beyond hysteresis threshold?
release(BUTTON_UP); // Release up key
yState = 0; // and set state to neutral center zone
}
}
// See note above re: not an else
if(!yState) { // Stick Y previously in neutral center zone?
if(pos > (1023 * 4 / 5)) { // Moved down?
Keyboard.press(KEY_DOWN); // Press down key
yState = 1; // and set state to down
} else if(pos < (1023 / 5)) { // Else moved up?
Keyboard.press(KEY_UP); // Press up key
yState = -1; // and set state to up
if(!yState) { // Stick Y previously in neutral center zone?
if(pos > (1023 * 4 / 5)) { // Moved down?
press(BUTTON_DOWN); // Press down key
yState = 1; // and set state to down
} else if(pos < (1023 / 5)) { // Else moved up?
press(BUTTON_UP); // Press up key
yState = -1; // and set state to up
}
}
@ -206,15 +349,15 @@ void loop() {
b = arcada.justPressedButtons() & ~MASK_BUTTONS;
stickmask |= b;
if(b & ARCADA_BUTTONMASK_RIGHT) Keyboard.press(KEY_RIGHT);
if(b & ARCADA_BUTTONMASK_LEFT) Keyboard.press(KEY_LEFT);
if(b & ARCADA_BUTTONMASK_DOWN) Keyboard.press(KEY_DOWN);
if(b & ARCADA_BUTTONMASK_UP) Keyboard.press(KEY_UP);
if(b & ARCADA_BUTTONMASK_RIGHT) press(BUTTON_RIGHT);
if(b & ARCADA_BUTTONMASK_LEFT) press(BUTTON_LEFT);
if(b & ARCADA_BUTTONMASK_DOWN) press(BUTTON_DOWN);
if(b & ARCADA_BUTTONMASK_UP) press(BUTTON_UP);
b = arcada.justReleasedButtons() & ~MASK_BUTTONS;
if(b & ARCADA_BUTTONMASK_RIGHT) Keyboard.release(KEY_RIGHT);
if(b & ARCADA_BUTTONMASK_LEFT) Keyboard.release(KEY_LEFT);
if(b & ARCADA_BUTTONMASK_DOWN) Keyboard.release(KEY_DOWN);
if(b & ARCADA_BUTTONMASK_UP) Keyboard.release(KEY_UP);
if(b & ARCADA_BUTTONMASK_RIGHT) release(BUTTON_RIGHT);
if(b & ARCADA_BUTTONMASK_LEFT) release(BUTTON_LEFT);
if(b & ARCADA_BUTTONMASK_DOWN) release(BUTTON_DOWN);
if(b & ARCADA_BUTTONMASK_UP) release(BUTTON_UP);
stickmask &= ~b;
// If there's no stick input, have Joy look up, as if watching game.
int dir = stickmask ? stickmask : ARCADA_BUTTONMASK_UP;
@ -238,6 +381,8 @@ void loop() {
}
#endif
sendKeys(); // Convert button state to HID
// Joy's eyes don't directly follow the joystick. They're always
// looking in some direction, pupils are kept around the perimeter
// of the eyes.

121
Joy_of_Arcada/keys.h Normal file
View file

@ -0,0 +1,121 @@
// This table maps key name strings (e.g. "RETURN") to
// numeric HID key codes in the TinyUSB hid.h header file.
const struct {
char *name;
uint8_t code;
} key[] = {
{ "A", HID_KEY_A },
{ "B", HID_KEY_B },
{ "C", HID_KEY_C },
{ "D", HID_KEY_D },
{ "E", HID_KEY_E },
{ "F", HID_KEY_F },
{ "G", HID_KEY_G },
{ "H", HID_KEY_H },
{ "I", HID_KEY_I },
{ "J", HID_KEY_J },
{ "K", HID_KEY_K },
{ "L", HID_KEY_L },
{ "M", HID_KEY_M },
{ "N", HID_KEY_N },
{ "O", HID_KEY_O },
{ "P", HID_KEY_P },
{ "Q", HID_KEY_Q },
{ "R", HID_KEY_R },
{ "S", HID_KEY_S },
{ "T", HID_KEY_T },
{ "U", HID_KEY_U },
{ "V", HID_KEY_V },
{ "W", HID_KEY_W },
{ "X", HID_KEY_X },
{ "Y", HID_KEY_Y },
{ "Z", HID_KEY_Z },
{ "1", HID_KEY_1 },
{ "2", HID_KEY_2 },
{ "3", HID_KEY_3 },
{ "4", HID_KEY_4 },
{ "5", HID_KEY_5 },
{ "6", HID_KEY_6 },
{ "7", HID_KEY_7 },
{ "8", HID_KEY_8 },
{ "9", HID_KEY_9 },
{ "0", HID_KEY_0 },
{ "RETURN", HID_KEY_RETURN },
{ "ESCAPE", HID_KEY_ESCAPE },
{ "BACKSPACE", HID_KEY_BACKSPACE },
{ "TAB", HID_KEY_TAB },
{ "SPACE", HID_KEY_SPACE },
{ "MINUS", HID_KEY_MINUS },
{ "EQUAL", HID_KEY_EQUAL },
{ "LEFT_BRACKET", HID_KEY_BRACKET_LEFT },
{ "RIGHT_BRACKET", HID_KEY_BRACKET_RIGHT },
{ "BACKSLASH", HID_KEY_BACKSLASH },
{ "EUROPE_1", HID_KEY_EUROPE_1 },
{ "SEMICOLON", HID_KEY_SEMICOLON },
{ "APOSTROPHE", HID_KEY_APOSTROPHE },
{ "GRAVE", HID_KEY_GRAVE },
{ "COMMA", HID_KEY_COMMA },
{ "PERIOD", HID_KEY_PERIOD },
{ "SLASH", HID_KEY_SLASH },
{ "CAPS_LOCK", HID_KEY_CAPS_LOCK },
{ "F1", HID_KEY_F1 },
{ "F2", HID_KEY_F2 },
{ "F3", HID_KEY_F3 },
{ "F4", HID_KEY_F4 },
{ "F5", HID_KEY_F5 },
{ "F6", HID_KEY_F6 },
{ "F7", HID_KEY_F7 },
{ "F8", HID_KEY_F8 },
{ "F9", HID_KEY_F9 },
{ "F10", HID_KEY_F10 },
{ "F11", HID_KEY_F11 },
{ "F12", HID_KEY_F12 },
{ "PRINT_SCREEN", HID_KEY_PRINT_SCREEN },
{ "SCROLL_LOCK", HID_KEY_SCROLL_LOCK },
{ "PAUSE", HID_KEY_PAUSE },
{ "INSERT", HID_KEY_INSERT },
{ "HOME", HID_KEY_HOME },
{ "PAGE_UP", HID_KEY_PAGE_UP },
{ "DELETE", HID_KEY_DELETE },
{ "END", HID_KEY_END },
{ "PAGE_DOWN", HID_KEY_PAGE_DOWN },
{ "RIGHT_ARROW", HID_KEY_ARROW_RIGHT },
{ "LEFT_ARROW", HID_KEY_ARROW_LEFT },
{ "DOWN_ARROW", HID_KEY_ARROW_DOWN },
{ "UP_ARROW", HID_KEY_ARROW_UP },
{ "NUM_LOCK", HID_KEY_NUM_LOCK },
{ "KEYPAD_DIVIDE", HID_KEY_KEYPAD_DIVIDE },
{ "KEYPAD_MULTIPLY", HID_KEY_KEYPAD_MULTIPLY },
{ "KEYPAD_SUBTRACT", HID_KEY_KEYPAD_SUBTRACT },
{ "KEYPAD_ADD", HID_KEY_KEYPAD_ADD },
{ "KEYPAD_ENTER", HID_KEY_KEYPAD_ENTER },
{ "KEYPAD_1", HID_KEY_KEYPAD_1 },
{ "KEYPAD_2", HID_KEY_KEYPAD_2 },
{ "KEYPAD_3", HID_KEY_KEYPAD_3 },
{ "KEYPAD_4", HID_KEY_KEYPAD_4 },
{ "KEYPAD_5", HID_KEY_KEYPAD_5 },
{ "KEYPAD_6", HID_KEY_KEYPAD_6 },
{ "KEYPAD_7", HID_KEY_KEYPAD_7 },
{ "KEYPAD_8", HID_KEY_KEYPAD_8 },
{ "KEYPAD_9", HID_KEY_KEYPAD_9 },
{ "KEYPAD_0", HID_KEY_KEYPAD_0 },
{ "KEYPAD_DECIMAL", HID_KEY_KEYPAD_DECIMAL },
{ "EUROPE_2", HID_KEY_EUROPE_2 },
{ "APPLICATION", HID_KEY_APPLICATION },
{ "POWER", HID_KEY_POWER },
{ "KEYPAD_EQUAL", HID_KEY_KEYPAD_EQUAL },
{ "F13", HID_KEY_F13 },
{ "F14", HID_KEY_F14 },
{ "F15", HID_KEY_F15 },
{ "LEFT_CONTROL", HID_KEY_CONTROL_LEFT },
{ "LEFT_SHIFT", HID_KEY_SHIFT_LEFT },
{ "LEFT_ALT", HID_KEY_ALT_LEFT },
{ "LEFT_GUI", HID_KEY_GUI_LEFT },
{ "RIGHT_CONTROL", HID_KEY_CONTROL_RIGHT },
{ "RIGHT_SHIFT", HID_KEY_SHIFT_RIGHT },
{ "RIGHT_ALT", HID_KEY_ALT_RIGHT },
{ "RIGHT_GUI", HID_KEY_GUI_RIGHT }
};
#define NUM_KEYS (sizeof(key) / sizeof(key[0]))