From e7ed72dfc0d5ae7d3fef0d176a0e83a8ede10793 Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Mon, 23 Dec 2024 20:59:00 +0300 Subject: [PATCH] twnlua: bindgen.py capable of converting share/twn_api.h spec to lua bindings --- .editorconfig | 2 +- apps/twnlua/CMakeLists.txt | 9 + apps/twnlua/bindgen.py | 112 +++++++++ apps/twnlua/data/scripts/game.lua | 13 +- apps/twnlua/game.c | 210 +--------------- share/twn_api.json | 406 ++++++++++++++++++++++++++++++ 6 files changed, 540 insertions(+), 212 deletions(-) create mode 100755 apps/twnlua/bindgen.py create mode 100644 share/twn_api.json diff --git a/.editorconfig b/.editorconfig index 29eb12f..c314740 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true -[*.{c,h}] +[*.{c,h,py}] indent_style = space indent_size = 4 diff --git a/apps/twnlua/CMakeLists.txt b/apps/twnlua/CMakeLists.txt index 01dec8f..6ab21f1 100644 --- a/apps/twnlua/CMakeLists.txt +++ b/apps/twnlua/CMakeLists.txt @@ -8,10 +8,19 @@ endif() add_subdirectory($ENV{TWNROOT} $ENV{TWNBUILDDIR}) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c + COMMAND ${PYTHON3} ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py $ENV{TWNROOT}/share/twn_api.json > ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindgen.py +) + + set(SOURCE_FILES game.c state.h + ${CMAKE_CURRENT_SOURCE_DIR}/luabind.c + lua/src/lapi.c lua/src/lapi.h lua/src/lauxlib.c diff --git a/apps/twnlua/bindgen.py b/apps/twnlua/bindgen.py new file mode 100755 index 0000000..9079c21 --- /dev/null +++ b/apps/twnlua/bindgen.py @@ -0,0 +1,112 @@ +#!/bin/env python3 + +import sys, json + +with open(sys.argv[1], 'r') if sys.argv[1] != "-" else sys.stdin as f: + api_source = f.read() + +api = json.loads(api_source) + + +def default(parameter): + basetype = parameter["type"].rsplit(' *', 1)[0] + if parameter["type"] == "float": + return parameter["default"] + elif parameter["type"] == "bool": + return "true" if parameter["default"] else "false" + elif parameter["type"] == "char *": + if parameter["default"] == {}: + return "NULL" + else: return '"' + parameter["default"] + '"' + elif basetype in api["types"]: + if parameter["type"].endswith("*"): + if parameter["default"] == {}: + return "NULL" + else: + return "&(%s){\n%s\n }" % \ + (parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items())) + else: + return "(%s){\n%s\n }" % \ + (parameter["type"], ", \n".join(" .%s = %s" % (n, v) for n, v in parameter["default"].items())) + raise BaseException("Unhandled default value of type '%s'" % parameter["type"]) + + +print('#include "twn_game_api.h"') +print('#include ') +print('#include ') +print('#include \n') + + +for typename, typedesc in api["types"].items(): + converter = "static %s table_to_%s(lua_State *L, int idx) {\n" % (typename, typename.lower()) + converter += " %s %s;\n" % (typename, typename.lower()); + + if not "fields" in typedesc: + continue + + for field in typedesc["fields"]: + converter += " lua_getfield(L, idx, \"%s\");\n" % (field["name"]); + if field["type"] == "float": + converter += " %s.%s = (float)lua_tonumber(L, -1);\n" % (typename.lower(), field["name"]); + elif field["type"] == "uint8_t": + converter += " %s.%s = (uint8_t)lua_tointeger(L, -1);\n" % (typename.lower(), field["name"]); + else: + raise BaseException("Unhandled converter field type '%s'" % (field["type"])) + converter += " lua_pop(L, 1);\n"; + + converter += " return %s;\n}\n" % (typename.lower()) + print(converter) + + +for procedure, procedure_desc in api["procedures"].items(): + binding = "static int binding_%s(lua_State *L) {\n" % procedure + binding += " luaL_checktype(L, 1, LUA_TTABLE);\n" + + if "params" in procedure_desc: + for parameter in procedure_desc["params"]: + basetype = parameter["type"].rsplit(' *', 1)[0] + + if parameter["type"].endswith("*"): + binding += " %s %s_value;\n" % (basetype, parameter["name"]) + binding += " %s %s;\n" % (parameter["type"], parameter["name"]) + binding += " lua_getfield(L, 1, \"%s\");\n" % parameter["name"] + + if "default" in parameter: + binding += " if (lua_isnoneornil(L, -1))\n" + binding += " %s = %s;\n" % (parameter["name"], default(parameter)) + binding += " else\n " + + if parameter["type"] == "float": + binding += " %s = (float)lua_tonumber(L, -1);\n" % (parameter["name"]); + elif parameter["type"] == "bool": + binding += " %s = lua_toboolean(L, -1);\n" % (parameter["name"]); + elif parameter["type"] == "char *": + binding += " %s = lua_tostring(L, -1);\n" % (parameter["name"]); + elif basetype in api["types"]: + if "enums" in api["types"][basetype]: + binding += " %s = lua_tointeger(L, -1);\n" % (parameter["name"]); + elif parameter["type"].endswith("*"): + binding += " { %s_value = table_to_%s(L, -1); %s = &%s_value; }\n" % (parameter["name"], basetype.lower(), parameter["name"], parameter["name"]); + else: + binding += " %s = table_to_%s(L, -1);\n" % (parameter["name"], basetype.lower()); + else: + raise BaseException("Unhandled parameter type '%s'" % (parameter["type"])) + + binding += " %s(%s);\n" % (procedure, ", ".join(param["name"] for param in procedure_desc["params"])) + binding += "}\n" + print(binding) + + +loader = "void bindgen_load_%s(lua_State *L) {\n" % api["name"] +modules = set(api["procedures"][procedure]["module"] for procedure in api["procedures"]) +for module in modules: + loader += " lua_newtable(L);\n" + for procedure, procedure_desc in api["procedures"].items(): + if procedure_desc["module"] == module: + loader += " lua_pushstring(L, \"%s\");\n" % procedure_desc["symbol"] + loader += " lua_pushcfunction(L, binding_%s);\n" % procedure + loader += " lua_settable(L, -3);\n" + loader += " lua_setglobal(L, \"%s\");\n" % module + +loader += "}" +print(loader) diff --git a/apps/twnlua/data/scripts/game.lua b/apps/twnlua/data/scripts/game.lua index 83cf84d..f9ead71 100644 --- a/apps/twnlua/data/scripts/game.lua +++ b/apps/twnlua/data/scripts/game.lua @@ -9,17 +9,8 @@ function game_tick() color = { r = 127, g = 0, b = 127, a = 255 }, } - input.action { - name = "move_up", - control = input.CONTROL_L, - } - - if input.action_pressed "move_up" then - draw.text { string = "BOO!" } - end - draw.sprite { - texture = "/assets/title.png", + path = "/assets/title.png", rect = { x = 320 - (320 / 2), y = 180 - (128 / 2), @@ -29,7 +20,7 @@ function game_tick() } draw.text { - string = "IT KEEPS HAPPENING", + string = "it never happened", position = offset, font = "/fonts/kenney-pixel.ttf", } diff --git a/apps/twnlua/game.c b/apps/twnlua/game.c index bc8c539..824436e 100644 --- a/apps/twnlua/game.c +++ b/apps/twnlua/game.c @@ -10,6 +10,10 @@ #include +/* generated by bindgen.py */ +void bindgen_load_twn(lua_State *L); + + /* require will go through physicsfs exclusively so that scripts can be in the data dir */ static int physfs_loader(lua_State *L) { const char *name = luaL_checkstring(L, 1); @@ -37,201 +41,6 @@ static int physfs_loader(lua_State *L) { } -static Rect table_to_rect(lua_State *L, int idx, const char *name) { - /* types are checked here to help prevent unexpected results */ - Rect rect; - int is_num; - - lua_getfield(L, idx, "x"); - rect.x = (float)lua_tonumberx(L, -1, &is_num); - if (!is_num) - luaL_error(L, "bad field 'x' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, idx, "y"); - rect.y = (float)lua_tonumberx(L, -1, &is_num); - if (!is_num) - luaL_error(L, "bad field 'y' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, idx, "w"); - rect.w = (float)lua_tonumberx(L, -1, &is_num); - if (!is_num) - luaL_error(L, "bad field 'w' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); - lua_pop(L, 1); - - lua_getfield(L, idx, "h"); - rect.h = (float)lua_tonumberx(L, -1, &is_num); - if (!is_num) - luaL_error(L, "bad field 'h' in '%s' (number expected, got %s)", name, luaL_typename(L, -1)); - lua_pop(L, 1); - - return rect; -} - - -static Color table_to_color(lua_State *L, int idx) { - Color color; - - lua_getfield(L, idx, "r"); - color.r = (uint8_t)lua_tointeger(L, -1); - lua_pop(L, 1); - - lua_getfield(L, idx, "g"); - color.g = (uint8_t)lua_tointeger(L, -1); - lua_pop(L, 1); - - lua_getfield(L, idx, "b"); - color.b = (uint8_t)lua_tointeger(L, -1); - lua_pop(L, 1); - - lua_getfield(L, idx, "a"); - color.a = (uint8_t)lua_tointeger(L, -1); - lua_pop(L, 1); - - return color; -} - - -/* sprite(data [table]) */ -/* data should contain the following fields: */ -/* - * path [string] - * rect [table] - * texture_region [table], optional - * color [table], optional - * rotation [number], optional - * flip_x [boolean], optional - * flip_y [boolean], optional - * stretch [boolean], optional - */ -static int b_sprite(lua_State *L) { - luaL_checktype(L, 1, LUA_TTABLE); - - DrawSpriteArgs args = { 0 }; - - lua_getfield(L, 1, "path"); - const char *field_path = lua_tostring(L, -1); - if (field_path == NULL) - luaL_error(L, "bad field 'path' in 'data' (string expected, got %s)", luaL_typename(L, -1)); - args.path = field_path; - - lua_getfield(L, 1, "rect"); - if (!lua_istable(L, -1)) - luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1)); - args.rect = table_to_rect(L, -1, "data.rect"); - - lua_getfield(L, 1, "texture_region"); - if (lua_istable(L, -1)) { - args.texture_region_opt = table_to_rect(L, -1, "data.texture_region"); - args.texture_region_opt_set = true; - } - - lua_getfield(L, 1, "color"); - if (lua_istable(L, -1)) { - args.color_opt = table_to_color(L, -1); - args.color_opt_set = true; - } - - lua_getfield(L, 1, "rotation"); - if (lua_isnumber(L, -1)) { - args.rotation_opt = (float)lua_tonumber(L, -1); - args.rotation_opt_set = true; - } - - lua_getfield(L, 1, "flip_x"); - if (lua_isboolean(L, -1)) { - args.flip_x_opt = lua_toboolean(L, -1); - args.flip_x_opt_set = true; - } - - lua_getfield(L, 1, "flip_y"); - if (lua_isboolean(L, -1)) { - args.flip_y_opt = lua_toboolean(L, -1); - args.flip_y_opt_set = true; - } - - lua_getfield(L, 1, "stretch"); - if (lua_isboolean(L, -1)) { - args.stretch_opt = lua_toboolean(L, -1); - args.stretch_opt_set = true; - } - - draw_sprite_args(args); - return 0; -} - - -/* rectangle(data [table]) */ -/* data should contain the following fields: */ -/* - * rect [table] - * color [table], optional, defaults to white - */ -static int b_rectangle(lua_State *L) { - luaL_checktype(L, 1, LUA_TTABLE); - - lua_getfield(L, 1, "rect"); - if (!lua_istable(L, -1)) - luaL_error(L, "bad field 'rect' in 'data' (table expected, got %s)", luaL_typename(L, -1)); - Rect rect = table_to_rect(L, -1, "data.rect"); - - Color color = { 255, 255, 255, 255 }; - lua_getfield(L, 1, "color"); - if (lua_istable(L, -1)) - color = table_to_color(L, -1); - - draw_rectangle(rect, color); - return 0; -} - - -/* text(data [table]) */ -/* data should contain the following fields: */ -/* - * string [string] - * position [table] - * height_px [number], optional, defaults to 22 - * color [table], optional, defaults to black - * font [string] - */ -static int b_text(lua_State *L) { - luaL_checktype(L, 1, LUA_TTABLE); - - lua_getfield(L, 1, "string"); - const char *string = lua_tostring(L, -1); - if (string == NULL) - luaL_error(L, "bad field 'string' in 'data' (string expected, got %s)", luaL_typename(L, -1)); - - lua_getfield(L, 1, "position"); - if (!lua_istable(L, -1)) - luaL_error(L, "bad field 'position' in 'data' (table expected, got %s)", luaL_typename(L, -1)); - lua_getfield(L, -1, "x"); - float x = (float)lua_tonumber(L, -1); - lua_getfield(L, -2, "y"); - float y = (float)lua_tonumber(L, -1); - - lua_getfield(L, 1, "height_px"); - int is_num; - int height_px = (int)lua_tointegerx(L, -1, &is_num); - if (!is_num) - height_px = 22; - - lua_getfield(L, 1, "color"); - Color color = { 0, 0, 0, 255 }; - if (lua_istable(L, -1)) - color = table_to_color(L, -1); - - lua_getfield(L, 1, "font"); - const char *font = lua_tostring(L, -1); - if (font == NULL) - luaL_error(L, "bad field 'font' in 'data' (string expected, got %s)", luaL_typename(L, -1)); - - draw_text(string, (Vec2) { x, y }, height_px, color, font); - return 0; -} - - void game_tick(void) { if (ctx.initialization_needed) { if (!ctx.udata) @@ -246,14 +55,13 @@ void game_tick(void) { } state->L = luaL_newstate(); - /* fakey version of luaL_openlibs() that excludes file i/o */ + /* fakey version of luaL_openlibs() that excludes file i/o and os stuff */ { static const luaL_Reg loaded_libs[] = { { LUA_GNAME, luaopen_base }, { LUA_LOADLIBNAME, luaopen_package }, { LUA_COLIBNAME, luaopen_coroutine }, { LUA_TABLIBNAME, luaopen_table }, - { LUA_OSLIBNAME, luaopen_os }, { LUA_STRLIBNAME, luaopen_string }, { LUA_MATHLIBNAME, luaopen_math }, { LUA_UTF8LIBNAME, luaopen_utf8 }, @@ -280,9 +88,11 @@ void game_tick(void) { lua_pop(state->L, 2); /* binding */ - lua_register(state->L, "sprite", b_sprite); - lua_register(state->L, "rectangle", b_rectangle); - lua_register(state->L, "text", b_text); + // lua_register(state->L, "sprite", b_sprite); + // lua_register(state->L, "rectangle", b_rectangle); + // lua_register(state->L, "text", b_text); + + bindgen_load_twn(state->L); /* now finally get to running the code */ unsigned char *game_buf = NULL; diff --git a/share/twn_api.json b/share/twn_api.json new file mode 100644 index 0000000..ac9ffd1 --- /dev/null +++ b/share/twn_api.json @@ -0,0 +1,406 @@ +{ + "name": "twn", + + "procedures": { + "input_action": { + "module": "input", + "symbol": "bind_action", + "header": "twn_input.h", + "params": [ + { "name": "name", "type": "char *" }, + { "name": "control", "type": "Control" } + ] + }, + + "input_action_pressed": { + "module": "input", + "symbol": "action_pressed", + "header": "twn_input.h", + "params": [ + { "name": "name", "type": "char *" } + ], + "return": "bool" + }, + + "input_action_just_pressed": { + "module": "input", + "symbol": "action_just_pressed", + "header": "twn_input.h", + "params": [ + { "name": "name", "type": "char *" } + ], + "return": "bool" + }, + + "input_action_just_released": { + "module": "input", + "symbol": "action_just_released", + "header": "twn_input.h", + "params": [ + { "name": "name", "type": "char *" } + ], + "return": "bool" + }, + + "input_action_position": { + "module": "input", + "symbol": "get_action_position", + "header": "twn_input.h", + "params": [ + { "name": "name", "type": "char *" } + ], + "return": "Vec2" + }, + + "input_mouse_captured": { + "module": "input", + "symbol": "set_mouse_captured", + "header": "twn_input.h", + "params": [ + { "name": "enabled", "type": "bool" } + ] + }, + + "draw_sprite": { + "module": "draw", + "symbol": "sprite", + "header": "twn_draw.h", + "params": [ + { "name": "path", "type": "char *" }, + { "name": "rect", "type": "Rect" }, + { "name": "texture_region", "type": "Rect *", "default": {} }, + { "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }, + { "name": "rotation", "type": "float", "default": 0.0 }, + { "name": "flip_x", "type": "bool", "default": false }, + { "name": "flip_y", "type": "bool", "default": false }, + { "name": "stretch", "type": "bool", "default": true } + ] + }, + + "draw_rectangle": { + "module": "draw", + "symbol": "rectangle", + "header": "twn_draw.h", + "params": [ + { "name": "rect", "type": "Rect" }, + { "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } } + ] + }, + + "draw_circle": { + "module": "draw", + "symbol": "circle", + "header": "twn_draw.h", + "params": [ + { "name": "position", "type": "Vec2" }, + { "name": "radius", "type": "float" }, + { "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } } + ] + }, + + "draw_text": { + "module": "draw", + "symbol": "text", + "header": "twn_draw.h", + "params": [ + { "name": "string", "type": "char *" }, + { "name": "position", "type": "Vec2" }, + { "name": "height", "type": "float", "default": 22 }, + { "name": "color", "type": "Color", "default": { "r": 255, "g": 255, "b": 255, "a": 255 } }, + { "name": "font", "type": "char *", "default": {} } + ] + }, + + "draw_text_width": { + "module": "draw", + "symbol": "text_width", + "header": "twn_draw.h", + "params": [ + { "name": "string", "type": "char *" }, + { "name": "height", "type": "float", "default": 22 }, + { "name": "font", "type": "char *", "default": {} } + ], + "return": "float" + } + }, + + "types": { + "Vec2": { + "fields": [ + { "name": "x", "type": "float" }, + { "name": "y", "type": "float" } + ] + }, + + "Vec3": { + "fields": [ + { "name": "x", "type": "float" }, + { "name": "y", "type": "float" }, + { "name": "z", "type": "float" } + ] + }, + + "Vec4": { + "fields": [ + { "name": "x", "type": "float" }, + { "name": "y", "type": "float" }, + { "name": "z", "type": "float" }, + { "name": "w", "type": "float" } + ] + }, + + "Color": { + "fields": [ + { "name": "r", "type": "uint8_t" }, + { "name": "g", "type": "uint8_t" }, + { "name": "b", "type": "uint8_t" }, + { "name": "a", "type": "uint8_t" } + ] + }, + + "Rect": { + "fields": [ + { "name": "x", "type": "float" }, + { "name": "y", "type": "float" }, + { "name": "w", "type": "float" }, + { "name": "h", "type": "float" } + ] + }, + + "Control": { + "enums": { + "A": 4, + "B": 5, + "C": 6, + "D": 7, + "E": 8, + "F": 9, + "G": 10, + "H": 11, + "I": 12, + "J": 13, + "K": 14, + "L": 15, + "M": 16, + "N": 17, + "O": 18, + "P": 19, + "Q": 20, + "R": 21, + "S": 22, + "T": 23, + "U": 24, + "V": 25, + "W": 26, + "X": 27, + "Y": 28, + "Z": 29, + "1": 30, + "2": 31, + "3": 32, + "4": 33, + "5": 34, + "6": 35, + "7": 36, + "8": 37, + "9": 38, + "0": 39, + "RETURN": 40, + "ESCAPE": 41, + "BACKSPACE": 42, + "TAB": 43, + "SPACE": 44, + "MINUS": 45, + "EQUALS": 46, + "LEFTBRACKET": 47, + "RIGHTBRACKET": 48, + "BACKSLASH": 49, + "NONUSHASH": 50, + "SEMICOLON": 51, + "APOSTROPHE": 52, + "GRAVE": 53, + "COMMA": 54, + "PERIOD": 55, + "SLASH": 56, + "CAPSLOCK": 57, + "F1": 58, + "F2": 59, + "F3": 60, + "F4": 61, + "F5": 62, + "F6": 63, + "F7": 64, + "F8": 65, + "F9": 66, + "F10": 67, + "F11": 68, + "F12": 69, + "PRINTSCREEN": 70, + "SCROLLLOCK": 71, + "PAUSE": 72, + "INSERT": 73, + "HOME": 74, + "PAGEUP": 75, + "DELETE": 76, + "END": 77, + "PAGEDOWN": 78, + "RIGHT": 79, + "LEFT": 80, + "DOWN": 81, + "UP": 82, + "NUMLOCKCLEAR": 83, + "KP_DIVIDE": 84, + "KP_MULTIPLY": 85, + "KP_MINUS": 86, + "KP_PLUS": 87, + "KP_ENTER": 88, + "KP_1": 89, + "KP_2": 90, + "KP_3": 91, + "KP_4": 92, + "KP_5": 93, + "KP_6": 94, + "KP_7": 95, + "KP_8": 96, + "KP_9": 97, + "KP_0": 98, + "KP_PERIOD": 99, + "NONUSBACKSLASH": 100, + "APPLICATION": 101, + "POWER": 102, + "KP_EQUALS": 103, + "F13": 104, + "F14": 105, + "F15": 106, + "F16": 107, + "F17": 108, + "F18": 109, + "F19": 110, + "F20": 111, + "F21": 112, + "F22": 113, + "F23": 114, + "F24": 115, + "EXECUTE": 116, + "HELP": 117, + "MENU": 118, + "SELECT": 119, + "STOP": 120, + "AGAIN": 121, + "UNDO": 122, + "CUT": 123, + "COPY": 124, + "PASTE": 125, + "FIND": 126, + "MUTE": 127, + "VOLUMEUP": 128, + "VOLUMEDOWN": 129, + "KP_COMMA": 133, + "KP_EQUALSAS400": 134, + "INTERNATIONAL1": 135, + "INTERNATIONAL2": 136, + "INTERNATIONAL3": 137, + "INTERNATIONAL4": 138, + "INTERNATIONAL5": 139, + "INTERNATIONAL6": 140, + "INTERNATIONAL7": 141, + "INTERNATIONAL8": 142, + "INTERNATIONAL9": 143, + "LANG1": 144, + "LANG2": 145, + "LANG3": 146, + "LANG4": 147, + "LANG5": 148, + "LANG6": 149, + "LANG7": 150, + "LANG8": 151, + "LANG9": 152, + "ALTERASE": 153, + "SYSREQ": 154, + "CANCEL": 155, + "CLEAR": 156, + "PRIOR": 157, + "RETURN2": 158, + "SEPARATOR": 159, + "OUT": 160, + "OPER": 161, + "CLEARAGAIN": 162, + "CRSEL": 163, + "EXSEL": 164, + "KP_00": 176, + "KP_000": 177, + "THOUSANDSSEPARATOR": 178, + "DECIMALSEPARATOR": 179, + "CURRENCYUNIT": 180, + "CURRENCYSUBUNIT": 181, + "KP_LEFTPAREN": 182, + "KP_RIGHTPAREN": 183, + "KP_LEFTBRACE": 184, + "KP_RIGHTBRACE": 185, + "KP_TAB": 186, + "KP_BACKSPACE": 187, + "KP_A": 188, + "KP_B": 189, + "KP_C": 190, + "KP_D": 191, + "KP_E": 192, + "KP_F": 193, + "KP_XOR": 194, + "KP_POWER": 195, + "KP_PERCENT": 196, + "KP_LESS": 197, + "KP_GREATER": 198, + "KP_AMPERSAND": 199, + "KP_DBLAMPERSAND": 200, + "KP_VERTICALBAR": 201, + "KP_DBLVERTICALBAR": 202, + "KP_COLON": 203, + "KP_HASH": 204, + "KP_SPACE": 205, + "KP_AT": 206, + "KP_EXCLAM": 207, + "KP_MEMSTORE": 208, + "KP_MEMRECALL": 209, + "KP_MEMCLEAR": 210, + "KP_MEMADD": 211, + "KP_MEMSUBTRACT": 212, + "KP_MEMMULTIPLY": 213, + "KP_MEMDIVIDE": 214, + "KP_PLUSMINUS": 215, + "KP_CLEAR": 216, + "KP_CLEARENTRY": 217, + "KP_BINARY": 218, + "KP_OCTAL": 219, + "KP_DECIMAL": 220, + "KP_HEXADECIMAL": 221, + "LCTRL": 224, + "LSHIFT": 225, + "LALT": 226, + "LGUI": 227, + "RCTRL": 228, + "RSHIFT": 229, + "RALT": 230, + "RGUI": 231, + "MODE": 257, + "KBDILLUMTOGGLE": 278, + "KBDILLUMDOWN": 279, + "KBDILLUMUP": 280, + "EJECT": 281, + "SLEEP": 282, + "APP1": 283, + "APP2": 284, + "AUDIOREWIND": 285, + "AUDIOFASTFORWARD": 286, + "SOFTLEFT": 287, + "SOFTRIGHT": 288, + "CALL": 289, + "ENDCALL": 290, + "LEFT_MOUSE": 513, + "RIGHT_MOUSE": 515, + "MIDDLE_MOUSE": 514, + "X1": 516, + "X2": 517 + } + } + } +}