--- /dev/null
+/*
+ * This wrapper is Copyright (c) 2020 S. Gilles <
[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ *
+ * libtermkey is Copyright (c) 2007-2011 Paul Evans <
[email protected]>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdio.h>
+#include <string.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include <termkey.h>
+
+#define STR(s) STR2(s)
+#define STR2(s) #s
+
+#define WRAP_TERMKEY_NAME "termkey.TermKey*"
+
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 501
+static void lua_setuservalue(lua_State *L, lua_Integer x) {
+        luaL_checktype(L, -1, LUA_TTABLE);
+        lua_setfenv(L, x);
+}
+#endif
+
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 502
+static void lua_seti(lua_State *L, lua_Integer i, lua_Integer d) {
+        lua_Integer j = i;
+        if (j < 0 && j > LUA_REGISTRYINDEX) {
+                j += lua_gettop(L) + 1;
+        }
+        lua_pushinteger(L, d);
+        lua_insert(L, -2);
+        lua_settable(L, j);
+}
+#endif
+
+static void table_to_fd_tk_flags(lua_State *L, int idx, int *out_fd, int *out_flags) {
+        int flags = 0;
+        size_t sk_len = 0;
+
+        lua_pushnil(L);
+        while (lua_next(L, idx)) {
+                const char *sk = 0;
+                lua_pushvalue(L, -2);
+                sk = lua_tolstring(L, -1, &sk_len);
+
+                if (!strncmp(sk, "fd", sk_len) && out_fd) {
+                        *out_fd = lua_tointegerx(L, -2, 0);
+                } else if (lua_toboolean(L, -2) && out_flags) {
+                        /*
+                           All flags default to false, so we
+                           need only set if value is non-false.
+                         */
+                        if (!strncmp(sk, "nointerpret", sk_len)) {
+                                flags |= TERMKEY_FLAG_NOINTERPRET;
+                        } else if (!strncmp(sk, "convertkp", sk_len)) {
+                                flags |= TERMKEY_FLAG_CONVERTKP;
+                        } else if (!strncmp(sk, "raw", sk_len)) {
+                                flags |= TERMKEY_FLAG_RAW;
+                        } else if (!strncmp(sk, "utf8", sk_len)) {
+                                flags |= TERMKEY_FLAG_UTF8;
+                        } else if (!strncmp(sk, "notermios", sk_len)) {
+                                flags |= TERMKEY_FLAG_NOTERMIOS;
+                        } else if (!strncmp(sk, "spacesymbol", sk_len)) {
+                                flags |= TERMKEY_FLAG_SPACESYMBOL;
+                        } else if (!strncmp(sk, "ctrlc", sk_len)) {
+                                flags |= TERMKEY_FLAG_CTRLC;
+                        } else if (!strncmp(sk, "eintr", sk_len)) {
+                                flags |= TERMKEY_FLAG_EINTR;
+                        } else if (!strncmp(sk, "nostart", sk_len)) {
+                                flags |= TERMKEY_FLAG_NOSTART;
+                        }
+                }
+                lua_pop(L, 2);
+        }
+
+        if (out_flags) {
+                *out_flags = flags;
+        }
+}
+
+static void table_to_canon_flags(lua_State *L, int idx, int *out_flags) {
+        int flags = 0;
+        size_t sk_len = 0;
+
+        lua_pushnil(L);
+        while (lua_next(L, idx)) {
+                const char *sk = 0;
+                lua_pushvalue(L, -2);
+                sk = lua_tolstring(L, -1, &sk_len);
+
+                if (lua_toboolean(L, -2) && out_flags) {
+                        if (!strncmp(sk, "spacesymbol", sk_len)) {
+                                flags |= TERMKEY_CANON_SPACESYMBOL;
+                        } else if (!strncmp(sk, "delbs", sk_len)) {
+                                flags |= TERMKEY_CANON_DELBS;
+                        }
+                }
+                lua_pop(L, 2);
+        }
+
+        if (out_flags) {
+                *out_flags = flags;
+        }
+}
+
+
+int wrap_termkey_start(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        termkey_start(*tk);
+
+        return 0;
+}
+
+int wrap_termkey_stop(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        termkey_stop(*tk);
+
+        return 0;
+}
+
+int wrap_termkey_is_started(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        lua_pushboolean(L, termkey_is_started(*tk));
+
+        return 1;
+}
+
+int wrap_termkey_get_fd(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        lua_pushinteger(L, termkey_get_fd(*tk));
+
+        return 1;
+}
+
+int wrap_termkey_get_flags(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        int flags = termkey_get_flags(*tk);
+
+        lua_createtable(L, 0, 9);
+
+        lua_pushboolean(L, !!(flags & TERMKEY_FLAG_NOINTERPRET));
+        lua_setfield(L, -2, "nointerpret");
+
+        lua_pushboolean(L, !!(flags & TERMKEY_FLAG_CONVERTKP));
+        lua_setfield(L, -2, "convertkp");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_RAW));
+        lua_setfield(L, -2, "raw");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_UTF8));
+        lua_setfield(L, -2, "utf8");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_NOTERMIOS));
+        lua_setfield(L, -2, "notermios");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_SPACESYMBOL));
+        lua_setfield(L, -2, "spacesymbol");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_CTRLC));
+        lua_setfield(L, -2, "ctrlc");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_EINTR));
+        lua_setfield(L, -2, "eintr");
+
+        lua_pushboolean(L, !!(flags &TERMKEY_FLAG_NOSTART));
+        lua_setfield(L, -2, "nostart");
+
+        return 1;
+}
+
+int wrap_termkey_set_flags(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        int flags = 0;
+
+        if (lua_istable(L, 2)) {
+                table_to_fd_tk_flags(L, 2, 0, &flags);
+        }
+
+        termkey_set_flags(*tk, flags);
+
+        return 0;
+}
+
+int wrap_termkey_get_waittime(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        lua_pushinteger(L, termkey_get_waittime(*tk));
+
+        return 1;
+}
+
+int wrap_termkey_set_waittime(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        int valid = 0;
+        int waittime = lua_tointegerx(L, 2, &valid);
+
+        if (valid) {
+               termkey_set_waittime(*tk, waittime);
+        }
+
+        return 0;
+}
+
+int wrap_termkey_get_canonflags(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        int flags = termkey_get_canonflags(*tk);
+
+        lua_createtable(L, 0, 2);
+
+        lua_pushboolean(L, !!(flags & TERMKEY_CANON_SPACESYMBOL));
+        lua_setfield(L, -2, "spacesymbol");
+
+        lua_pushboolean(L, !!(flags & TERMKEY_CANON_DELBS));
+        lua_setfield(L, -2, "delbs");
+
+        return 1;
+}
+
+int wrap_termkey_set_canonflags(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        int flags = 0;
+
+        if (lua_istable(L, 2)) {
+                table_to_canon_flags(L, 2, &flags);
+        }
+
+        termkey_set_canonflags(*tk, flags);
+
+        return 0;
+}
+
+int wrap_termkey_get_buffer_size(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        lua_pushinteger(L, termkey_get_buffer_size(*tk));
+
+        return 1;
+}
+
+int wrap_termkey_set_buffer_size(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        int valid = 0;
+        size_t buffer_size = lua_tointegerx(L, 2, &valid);
+
+        if (valid) {
+               termkey_set_buffer_size(*tk, buffer_size);
+        }
+
+        return 0;
+}
+
+int wrap_termkey_get_buffer_remaining(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        lua_pushinteger(L, termkey_get_buffer_remaining(*tk));
+
+        return 1;
+}
+
+static const char *string_from_sym(TermKeySym sym) {
+        switch(sym) {
+        case TERMKEY_SYM_UNKNOWN: return "unknown";
+        case TERMKEY_SYM_NONE: return "none";
+        case TERMKEY_SYM_BACKSPACE: return "backspace";
+        case TERMKEY_SYM_TAB: return "tab";
+        case TERMKEY_SYM_ENTER: return "enter";
+        case TERMKEY_SYM_ESCAPE: return "escape";
+        case TERMKEY_SYM_SPACE: return "space";
+        case TERMKEY_SYM_DEL: return "del";
+        case TERMKEY_SYM_UP: return "up";
+        case TERMKEY_SYM_DOWN: return "down";
+        case TERMKEY_SYM_LEFT: return "left";
+        case TERMKEY_SYM_RIGHT: return "right";
+        case TERMKEY_SYM_BEGIN: return "begin";
+        case TERMKEY_SYM_FIND: return "find";
+        case TERMKEY_SYM_INSERT: return "insert";
+        case TERMKEY_SYM_DELETE: return "delete";
+        case TERMKEY_SYM_SELECT: return "select";
+        case TERMKEY_SYM_PAGEUP: return "pageup";
+        case TERMKEY_SYM_PAGEDOWN: return "pagedown";
+        case TERMKEY_SYM_HOME: return "home";
+        case TERMKEY_SYM_END: return "end";
+        case TERMKEY_SYM_CANCEL: return "cancel";
+        case TERMKEY_SYM_CLEAR: return "clear";
+        case TERMKEY_SYM_CLOSE: return "close";
+        case TERMKEY_SYM_COMMAND: return "command";
+        case TERMKEY_SYM_COPY: return "copy";
+        case TERMKEY_SYM_EXIT: return "exit";
+        case TERMKEY_SYM_HELP: return "help";
+        case TERMKEY_SYM_MARK: return "mark";
+        case TERMKEY_SYM_MESSAGE: return "message";
+        case TERMKEY_SYM_MOVE: return "move";
+        case TERMKEY_SYM_OPEN: return "open";
+        case TERMKEY_SYM_OPTIONS: return "options";
+        case TERMKEY_SYM_PRINT: return "print";
+        case TERMKEY_SYM_REDO: return "redo";
+        case TERMKEY_SYM_REFERENCE: return "reference";
+        case TERMKEY_SYM_REFRESH: return "refresh";
+        case TERMKEY_SYM_REPLACE: return "replace";
+        case TERMKEY_SYM_RESTART: return "restart";
+        case TERMKEY_SYM_RESUME: return "resume";
+        case TERMKEY_SYM_SAVE: return "save";
+        case TERMKEY_SYM_SUSPEND: return "suspend";
+        case TERMKEY_SYM_UNDO: return "undo";
+        case TERMKEY_SYM_KP0: return "kp0";
+        case TERMKEY_SYM_KP1: return "kp1";
+        case TERMKEY_SYM_KP2: return "kp2";
+        case TERMKEY_SYM_KP3: return "kp3";
+        case TERMKEY_SYM_KP4: return "kp4";
+        case TERMKEY_SYM_KP5: return "kp5";
+        case TERMKEY_SYM_KP6: return "kp6";
+        case TERMKEY_SYM_KP7: return "kp7";
+        case TERMKEY_SYM_KP8: return "kp8";
+        case TERMKEY_SYM_KP9: return "kp9";
+        case TERMKEY_SYM_KPENTER: return "kpenter";
+        case TERMKEY_SYM_KPPLUS: return "kpplus";
+        case TERMKEY_SYM_KPMINUS: return "kpminus";
+        case TERMKEY_SYM_KPMULT: return "kpmult";
+        case TERMKEY_SYM_KPDIV: return "kpdiv";
+        case TERMKEY_SYM_KPCOMMA: return "kpcomma";
+        case TERMKEY_SYM_KPPERIOD: return "kpperiod";
+        case TERMKEY_SYM_KPEQUALS: return "kpequals";
+        default: return "unknown";
+        }
+}
+
+static int luaify_getkey_result(lua_State *L, TermKey *tk, TermKeyKey *k, TermKeyResult r) {
+        TermKeyMouseEvent me;
+        int button = 0, line = 0, col = 0, initial = 0, mode = 0, value = 0;
+        unsigned long cmd = 0;
+        long args[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+        size_t nargs = 16;
+        const char *str = 0;
+
+        switch(r) {
+        case TERMKEY_RES_NONE:
+                lua_pushnil(L);
+
+                return 1;
+        case TERMKEY_RES_EOF:
+                lua_pushliteral(L, "eof");
+
+                return 1;
+        case TERMKEY_RES_AGAIN:
+                lua_pushliteral(L, "again");
+
+                return 1;
+        case TERMKEY_RES_ERROR:
+                lua_pushliteral(L, "error");
+
+                return 1;
+        case TERMKEY_RES_KEY:
+                break;
+        }
+
+        lua_createtable(L, 0, 5);
+
+        lua_pushboolean(L, !!(k->modifiers & TERMKEY_KEYMOD_SHIFT));
+        lua_setfield(L, -2, "shift");
+        lua_pushboolean(L, !!(k->modifiers & TERMKEY_KEYMOD_ALT));
+        lua_setfield(L, -2, "alt");
+        lua_pushboolean(L, !!(k->modifiers & TERMKEY_KEYMOD_CTRL));
+        lua_setfield(L, -2, "ctrl");
+
+        if (k->utf8[0]) {
+                lua_pushstring(L, k->utf8);
+                lua_setfield(L, -2, "utf8");
+        }
+
+        switch(k->type) {
+        case TERMKEY_TYPE_UNICODE:
+                lua_pushinteger(L, k->code.codepoint);
+                lua_setfield(L, -2, "codepoint");
+                break;
+        case TERMKEY_TYPE_FUNCTION:
+                lua_pushinteger(L, k->code.number);
+                lua_setfield(L, -2, "function_number");
+                break;
+        case TERMKEY_TYPE_KEYSYM:
+                lua_pushstring(L, string_from_sym(k->code.sym));
+                lua_setfield(L, -2, "sym");
+                break;
+        case TERMKEY_TYPE_MOUSE:
+                termkey_interpret_mouse(tk, k, &me, &button, &line, &col);
+                lua_createtable(L, 0, 4);
+
+                lua_pushinteger(L, button);
+                lua_setfield(L, -2, "button");
+                lua_pushinteger(L, line);
+                lua_setfield(L, -2, "line");
+                lua_pushinteger(L, col);
+                lua_setfield(L, -2, "col");
+
+                switch(me) {
+                case TERMKEY_MOUSE_PRESS:
+                        lua_pushliteral(L, "press");
+                        break;
+                case TERMKEY_MOUSE_DRAG:
+                        lua_pushliteral(L, "drag");
+                        break;
+                case TERMKEY_MOUSE_RELEASE:
+                        lua_pushliteral(L, "release");
+                        break;
+                default:
+                        lua_pushliteral(L, "unknown");
+                        break;
+                }
+                lua_setfield(L, -2, "event");
+
+                lua_setfield(L, -2, "mouse");
+                break;
+        case TERMKEY_TYPE_POSITION:
+                termkey_interpret_position(tk, k, &line, &col);
+                lua_createtable(L, 0, 2);
+
+                lua_pushinteger(L, line);
+                lua_setfield(L, -2, "line");
+                lua_pushinteger(L, col);
+                lua_setfield(L, -2, "col");
+
+                lua_setfield(L, -2, "position");
+                break;
+        case TERMKEY_TYPE_MODEREPORT:
+                termkey_interpret_modereport(tk, k, &initial, &mode, &value);
+                lua_createtable(L, 0, 3);
+
+                lua_pushinteger(L, initial);
+                lua_setfield(L, -2, "initial");
+                lua_pushinteger(L, mode);
+                lua_setfield(L, -2, "mode");
+                lua_pushinteger(L, value);
+                lua_setfield(L, -2, "value");
+
+                lua_setfield(L, -2, "modereport");
+                break;
+        case TERMKEY_TYPE_DCS: /* fall through */
+        case TERMKEY_TYPE_OSC:
+                termkey_interpret_string(tk, k, &str);
+                lua_pushstring(L, str);
+                lua_setfield(L, -2, "command_string");
+                break;
+        case TERMKEY_TYPE_UNKNOWN_CSI:
+                termkey_interpret_csi(tk, k, args, &nargs, &cmd);
+                lua_createtable(L, 0, 2);
+                lua_createtable(L, 16, 0);
+
+                for (size_t j = 0; j < nargs; ++j) {
+                        lua_pushinteger(L, args[j]);
+                        lua_seti(L, -2, j + 1);
+                }
+
+                lua_setfield(L, -2, "args");
+
+                lua_pushinteger(L, cmd);
+                lua_setfield(L, -2, "cmd");
+                lua_setfield(L, -2, "csi");
+
+                break;
+        }
+
+        return 1;
+}
+
+int wrap_termkey_getkey(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        TermKeyKey k = { 0 };
+        TermKeyResult r = termkey_getkey(*tk, &k);
+
+        return luaify_getkey_result(L, *tk, &k, r);
+}
+
+int wrap_termkey_getkey_force(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        TermKeyKey k = { 0 };
+        TermKeyResult r = termkey_getkey_force(*tk, &k);
+
+        return luaify_getkey_result(L, *tk, &k, r);
+}
+
+int wrap_termkey_waitkey(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+        TermKeyKey k = { 0 };
+        TermKeyResult r = termkey_waitkey(*tk, &k);
+
+        return luaify_getkey_result(L, *tk, &k, r);
+}
+
+int wrap_termkey_gc(lua_State *L) {
+        TermKey **tk = (TermKey **) luaL_checkudata(L, 1, WRAP_TERMKEY_NAME);
+
+        if (*tk) {
+                termkey_destroy(*tk);
+        }
+
+        return 0;
+}
+
+static const struct luaL_Reg tk_mapping[] = {
+        { "start", wrap_termkey_start },
+        { "stop", wrap_termkey_stop },
+        { "is_started", wrap_termkey_is_started },
+        { "get_fd", wrap_termkey_get_fd },
+        { "get_flags", wrap_termkey_get_flags },
+        { "set_flags", wrap_termkey_set_flags },
+        { "get_waittime", wrap_termkey_get_waittime },
+        { "set_waittime", wrap_termkey_set_waittime },
+        { "get_canonflags", wrap_termkey_get_canonflags },
+        { "set_canonflags", wrap_termkey_set_canonflags },
+        { "get_buffer_size", wrap_termkey_get_buffer_size },
+        { "set_buffer_size", wrap_termkey_set_buffer_size },
+        { "get_buffer_remaining", wrap_termkey_get_buffer_remaining },
+        { "getkey", wrap_termkey_getkey },
+        { "getkey_force", wrap_termkey_getkey_force },
+        { "waitkey", wrap_termkey_waitkey },
+        { "__gc", wrap_termkey_gc },
+        { 0 },
+};
+
+
+static int wrap_new(lua_State *L) {
+        int flags = 0;
+        int fd = 0;
+
+        TermKey **tk = (TermKey **) lua_newuserdata(L, sizeof *tk);
+        luaL_getmetatable(L, WRAP_TERMKEY_NAME);
+        lua_pushvalue(L, -1);
+        lua_setuservalue(L, -3);
+        lua_setmetatable(L, -2);
+
+        if (0 == 1) {
+                luaL_setfuncs(L, tk_mapping, 0);
+        }
+
+        if (lua_istable(L, 1)) {
+                table_to_fd_tk_flags(L, 1, &fd, &flags);
+        }
+
+        *tk = termkey_new(fd, flags);
+
+        if (!*tk) {
+                lua_pop(L, 1);
+                lua_pushnil(L);
+        }
+
+        return 1;
+}
+
+static const struct luaL_Reg mapping[] = {
+        { "new", wrap_new },
+        { 0 },
+};
+
+/* Lua metatable initialization */
+int luaopen_termkey (lua_State *L) {
+        lua_newtable(L);
+        luaL_setfuncs(L, mapping, 0);
+        lua_pushvalue(L, -1);
+        lua_pushliteral(L, "Copyright (C) 2007-2011 Paul Evans <
[email protected]>");
+        lua_setfield(L, -2, "_COPYRIGHT");
+        lua_pushliteral(L, "This library allows easy processing of keyboard entry from terminal-based programs.");
+        lua_setfield(L, -2, "_DESCRIPTION");
+        lua_pushliteral(L, "lua-termkey " STR(TERMKEY_VERSION_MAJOR) "." STR(TERMKEY_VERSION_MINOR));
+        lua_setfield(L, -2, "_VERSION");
+
+        /*
+           Setting the metatable to be its own index allows mixing
+           metatable methods (__gc) and member functions (start) in the
+           same mapping.
+         */
+        luaL_newmetatable(L, WRAP_TERMKEY_NAME);
+        lua_pushvalue(L, -1);
+        lua_setfield(L, -2, "__index");
+        luaL_setfuncs(L, tk_mapping, 0);
+        lua_pop(L, 1);
+
+        return 1;
+}