initialmasterv0.22
authorS. Gilles <[email protected]>
Fri, 21 Feb 2020 15:35:35 +0000 (21 10:35 -0500)
committerS. Gilles <[email protected]>
Sat, 22 Feb 2020 07:27:07 +0000 (22 02:27 -0500)
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
demo.lua [new file with mode: 0644]
wrap-termkey.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c08e8fd
--- /dev/null
@@ -0,0 +1,3 @@
+core
+*.o
+*.so
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..c51c004
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,45 @@
+The MIT License
+
+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.
+
+Termkey is licensed under the following:
+
+The MIT License
+
+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.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..f475aec
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+PKG_CONFIG ?= pkg-config
+CFLAGS ?= $(shell $(PKG_CONFIG) --cflags lua)
+LDFLAGS ?= $(shell $(PKG_CONFIG) --libs lua)
+
+CFLAGS += -Wall -Werror -Wextra
+
+PREFIX ?= /usr
+BINDIR ?= $(PREFIX)/bin
+MANDIR ?= $(PREFIX)/share/man
+INSTALL_CMOD ?= $(shell $(PKG_CONFIG) --variable INSTALL_CMOD lua)
+INSTALL_LMOD ?= $(shell $(PKG_CONFIG) --variable INSTALL_LMOD lua)
+
+CFLAGS  += -fPIC -shared $(shell $(PKG_CONFIG) --cflags termkey)
+LDFLAGS +=               $(shell $(PKG_CONFIG) --libs   termkey)
+
+CC ?= gcc
+LD ?= gcc
+
+.PHONY: all clean install
+
+all: termkey.so
+
+clean:
+       rm termkey.so wrap-termkey.o
+
+install: termkey.so
+       mkdir -p $(DESTDIR)$(INSTALL_CMOD)
+       cp termkey.so $(DESTDIR)$(INSTALL_CMOD)
+
+%.o: %.c
+       $(CC) -c $(CFLAGS) -o $@ $^
+
+termkey.so: wrap-termkey.o
+       $(CC) -o $@ $^ $(LDFLAGS) -shared -fPIC
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..6db82a8
--- /dev/null
+++ b/README
@@ -0,0 +1,153 @@
+This is lua-termkey, lua bindings to LeoNerd's libtermkey. This was
+written against libtermkey-0.22.
+
+Libtermkey may be found at http://www.leonerd.org.uk/code/libtermkey
+
+You're probably reading this wanting to know how to translate between
+termkey.h and these bindings. So, assume you have written
+
+        local termkey = require("termkey")
+
+Now, here's what you just imported.
+
+ - tk = termkey.new(t)
+
+   Returns tk, a garbage-collected TermKey* object (the garbage
+   collection calls termkey_destroy, not termkey_free), or nil on error.
+   t should be a table, where the following keys have meaning (all the
+   booleans default to false).
+
+    - t["fd"] integer; the file descriptor to attach to tk. Defaults to 1.
+
+    - t["nointerpret"] boolean; true means "Do not interpret C0//DEL codes if possible"
+
+    - t["convertkp"] boolean; true means "Convert KP codes to regular keypresses"
+
+    - t["raw"] boolean; true means "Input is raw bytes, not UTF-8"
+
+    - t["utf8"] boolean; true means "Input is definitely UTF-8"
+
+    - t["notermios"] boolean; true means "Do not make initial termios calls on construction"
+
+    - t["spacesymbol"] boolean; true means "Sets TERMKEY_CANON_SPACESYMBOL"
+
+    - t["ctrlc"] boolean; true means "Allow Ctrl-C to be read as normal, disabling SIGINT"
+
+    - t["eintr"] boolean; true means "Return ERROR on signal (EINTR) rather than retry"
+
+    - t["nostart"] boolean; true means "Do not call termkey_start() in constructor"
+
+      Note: At the time of writing, with termkey 0.22, the following program
+
+          termkey=require('termkey')
+          tk=termkey.new({ nostart = true })
+          -- implicit end of program, and garbage collection of tk
+
+      is equivalent to
+
+          #include <termkey.h>
+          int main(void)
+          {
+              TermKey *tk = termkey_new(1, TERMKEY_FLAG_NOSTART);
+              termkey_destroy(tk);
+
+              return 0;
+          }
+
+      which crashes (on my machine and with my $TERM, at least). I'm not
+      going to put in convoluted workarounds for this -- wrapper
+      libraries aren't the place for patches.
+
+ - tk:start() calls termkey_start(tk)
+
+ - tk:stop() calls termkey_stop(tk)
+
+ - tk:is_started() returns termkey_is_started(tk) as a boolean
+
+ - tk:get_fd() returns termkey_get_fd(tk) as an integer
+
+ - tk:get_flags() calls termkey_get_flags(tk), then returns it as a
+   table such as
+
+     { "nointerpret" = false, "convertkp" = true, ... }
+
+   as described for termkey.new()
+
+ - tk:get_waittime() returns termkey_get_waittime(tk) as an integer
+
+ - tk:set_waittime(n) calls termkey_set_waittime(tk, n)
+
+ - tk:get_canonflags() returns something like
+
+     { spacesymbol = true, delbs = false }
+
+   based on termkey_get_canonflags(tk)
+
+ - tk:set_canonflags(t) calls termkey_set_canonflags(tk, flags), where
+   flags is interpreted from the table, t, based on the format described
+   above.
+
+ - tk:get_buffer_size() returns termkey_get_buffer_size(tk) as an
+   integer. Note that get_buffer_size returns size_t, but Lua will
+   convert this into Lua_integer, which could be smaller.
+
+ - tk:set_buffer_size(n) calls termkey_set_buffer_size(tk, n). As with
+   get_buffer_size, n may be truncated during the translation.
+
+ - tk:get_buffer_remaining() returns termkey_get_buffer_remaining(tk) as
+   an integer. See get_buffer_size.
+
+ - k = tk:getkey() calls termkey_getkey. If the result (the
+   TermKeyResult) is TERMKEY_RES_NONE, k is nil. It might also be "eof",
+   "again", or "error", corresponding to the appropriate return values
+   of termkey_getkey.
+
+   Finally, if termkey_getkey returned TERMKEY_RES_KEY, then k is a
+   table of the following form:
+
+     {
+       codepoint = <a unicode codepoint, as an integer>
+       function_number = <when F8 is pressed, this is 8>
+       sym = <a string, see below>
+       mouse = {
+         event = <one of "unknown", "press", "drag", "release">,
+         button = <integer>,
+         line = <integer>,
+         col = <integer>
+       }
+       modereport = {
+         initial = <integer>,
+         mode = <integer>,
+         value = <integer>
+       }
+       position = {
+         line = <integer>,
+         col = <integer>
+       }
+       command_string = <string>
+       csi = {
+         args = <an array of integers>
+         cmd = <integer>
+       }
+       -- exactly one of the above fields is present; the more complex
+       -- fields are filled out according to the termkey_interpret_xyz
+       -- family of functions.
+
+       shift = <boolean>
+       alt = <boolean>
+       ctrl = <boolean>
+       -- the above fields correspond to the modifiers field of
+       -- TermKeyKey and might be omitted, implying false
+
+       utf8 = <string>
+       -- If the utf8 field of TermKeyKey contains a non-trivial string,
+       -- it will be copied into this field, treated as a sequence of
+       -- bytes.
+     }
+
+   The value of the sym field is obtained by taking the name of the
+   value in the TermKeyKey structure (something like
+   "TERMKEY_SYM_DELETE"), deleting the "TERMKEY_SYM" prefix, and
+   lower-casing the result (obtaining "delete"). This includes "unknown"
+   and "none". In the impossible case of TERMKEY_N_SYMS, "unknown" is
+   returned.
diff --git a/demo.lua b/demo.lua
new file mode 100644 (file)
index 0000000..241a83c
--- /dev/null
+++ b/demo.lua
@@ -0,0 +1,30 @@
+local termkey = require('termkey')
+local inspect = require('inspect')
+
+local tk = termkey.new({ spacesymbol = true, ctrlc = true })
+
+if not tk then
+        print('Cannot allocate termkey instance')
+        return
+end
+
+if tk:get_flags()["utf8"] then
+        print('Termkey in UTF-8 mode')
+elseif tk:getflags()["raw"] then
+        print('Termkey in RAW mode')
+end
+
+print('\027[?1000h')
+
+local k = tk:waitkey()
+while k ~= "eof" and k ~= "error" do
+        print(inspect(k))
+
+        if k.ctrl and (k.utf8 == "c" or k.utf8 == "C") then
+                return
+        elseif not (k.ctrl or k.shift or k.alt) and k.utf8 == "?" then
+                print("\027[?1$p")
+        end
+
+        k = tk:waitkey()
+end
diff --git a/wrap-termkey.c b/wrap-termkey.c
new file mode 100644 (file)
index 0000000..8aa5b41
--- /dev/null
@@ -0,0 +1,614 @@
+/*
+ * 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;
+}