Download
593 lines 19.1 KiBCModified 2026-04-05 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
/*
* lbtui.c
* A Lua library binding for btui, Bruce's Text User Interface library.
*/

#include "lua.h"
#include "lauxlib.h"
#include "btui.h"

// The C API changed from 5.1 to 5.2, so these shims help the code compile on >=5.2
#if LUA_VERSION_NUM >= 502
#define lua_objlen(L, i) lua_rawlen(L, i)
#define luaL_register(L, _, R) luaL_setfuncs(L, R, 0)
#endif
// Lua 5.3 introduced lua_isinteger, fall back to lua_isnumber
#if LUA_VERSION_NUM < 503
#define lua_isinteger(L, i) lua_isnumber(L, i)
#endif

const int BTUI_METATABLE, BTUI_ATTRIBUTES, BTUI_INVERSE_ATTRIBUTES;

static int Lbtui_enable(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    const char *modestring = luaL_optlstring(L, 2, "TUI", NULL);
    btui_mode_t mode = BTUI_MODE_TUI;
    if (strcmp(modestring, "normal") == 0)
        mode = BTUI_MODE_NORMAL;
    else if (strcmp(modestring, "TUI") != 0)
        luaL_error(L, "Invalid BTUI mode");
    *bt = btui_create(mode);
    btui_move_cursor(*bt, 0, 0);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_disable(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    if (*bt == NULL) luaL_error(L, "BTUI object not initialized");
    btui_disable(*bt);
    return 0;
}

static int Lbtui_withdisabled(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int top = lua_gettop(L);
    if (top < 2) luaL_error(L, "No function provided");

    lua_pushcfunction(L, Lbtui_disable);
    lua_pushvalue(L, 1);
    lua_call(L, 1, 0);

    lua_pushvalue(L, 2);
    lua_call(L, 0, LUA_MULTRET);
    int top2 = lua_gettop(L);

    lua_pushcfunction(L, Lbtui_enable);
    lua_pushvalue(L, 1);
    lua_call(L, 1, 0);

    return top2 - top;
}

static int Lbtui_getkey(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    if (*bt == NULL) luaL_error(L, "BTUI object not initialized");
    int mouse_x = -1, mouse_y = -1;
    int timeout = lua_gettop(L) <= 1 ? -1 : (int)luaL_checkinteger(L, 2);
    int key = btui_getkey(*bt, timeout, &mouse_x, &mouse_y);
    if (key == -1) return 0;
    char buf[256] = {0};
    btui_keyname(key, buf);
    lua_pushstring(L, buf);
    if (mouse_x != -1 || mouse_y != -1) {
        lua_pushinteger(L, mouse_x);
        lua_pushinteger(L, mouse_y);
        return 3;
    }
    return 1;
}

static int Lbtui_write(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int n = lua_gettop(L);
    for (int i = 2; i <= n; i++) {
        btui_puts(*bt, luaL_tolstring(L, i, NULL));
        lua_pop(L, 1);
    }
    btui_flush(*bt);
    return 0;
}

static int Lbtui_clear(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    const char *cleartype = luaL_optlstring(L, 2, "screen", NULL);
    if (strcmp(cleartype, "screen") == 0) {
        btui_clear(*bt, BTUI_CLEAR_SCREEN);
    } else if (strcmp(cleartype, "below") == 0) {
        btui_clear(*bt, BTUI_CLEAR_BELOW);
    } else if (strcmp(cleartype, "above") == 0) {
        btui_clear(*bt, BTUI_CLEAR_ABOVE);
    } else if (strcmp(cleartype, "right") == 0) {
        btui_clear(*bt, BTUI_CLEAR_RIGHT);
    } else if (strcmp(cleartype, "left") == 0) {
        btui_clear(*bt, BTUI_CLEAR_LEFT);
    } else if (strcmp(cleartype, "line") == 0) {
        btui_clear(*bt, BTUI_CLEAR_LINE);
    } else {
        lua_pushliteral(L, "unknown clear type");
        lua_error(L);
    }
    btui_flush(*bt);
    return 0;
}

static int Lbtui_flush(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    btui_flush(*bt);
    return 0;
}

static int Lbtui_batch_begin(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    btui_batch_begin(*bt);
    return 0;
}

static int Lbtui_batch_end(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    btui_batch_end(*bt);
    return 0;
}

static int Lbtui_batch(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int nargs = lua_gettop(L)-2;
    btui_batch_begin(*bt);
    lua_call(L, nargs, LUA_MULTRET);
    return lua_gettop(L);
}

static int Lbtui_hidecursor(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    btui_hide_cursor(*bt);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_showcursor(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    btui_show_cursor(*bt);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_move(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    if (lua_gettop(L) < 3) luaL_error(L, "Expected x and y values");
    int x = (int)luaL_checkinteger(L, 2);
    int y = (int)luaL_checkinteger(L, 3);
    btui_move_cursor(*bt, x, y);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_setcursor(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    const char *cursortype = luaL_optlstring(L, 2, "default", NULL);
    if (strcmp(cursortype, "default") == 0) {
        btui_set_cursor(*bt, CURSOR_DEFAULT);
    } else if (strcmp(cursortype, "blinking block") == 0) {
        btui_set_cursor(*bt, CURSOR_BLINKING_BLOCK);
    } else if (strcmp(cursortype, "block") == 0) {
        btui_set_cursor(*bt, CURSOR_STEADY_BLOCK);
    } else if (strcmp(cursortype, "blinking underline") == 0) {
        btui_set_cursor(*bt, CURSOR_BLINKING_UNDERLINE);
    } else if (strcmp(cursortype, "underline") == 0) {
        btui_set_cursor(*bt, CURSOR_STEADY_UNDERLINE);
    } else if (strcmp(cursortype, "blinking bar") == 0) {
        btui_set_cursor(*bt, CURSOR_BLINKING_BAR);
    } else if (strcmp(cursortype, "bar") == 0) {
        btui_set_cursor(*bt, CURSOR_STEADY_BAR);
    } else {
        lua_pushliteral(L, "unknown cursor type");
        lua_error(L);
    }
    btui_flush(*bt);
    return 0;
}

static int Lbtui_setmode(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    const char *modestring = luaL_checkstring(L, 2);
    btui_mode_t mode = BTUI_MODE_TUI;
    if (strcmp(modestring, "normal") == 0)
        mode = BTUI_MODE_NORMAL;
    else if (strcmp(modestring, "TUI") != 0)
        luaL_error(L, "Invalid BTUI mode");
    btui_set_mode(*bt, mode);
    return 0;
}

static int Lbtui_withfg(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    if (lua_gettop(L) < 5) luaL_error(L, "Expected r,g,b values and a function");
    lua_Number r = luaL_checknumber(L, 2);
    lua_Number g = luaL_checknumber(L, 3);
    lua_Number b = luaL_checknumber(L, 4);
    btui_set_fg(*bt,
                r < 0.0 ? 0 : (r > 1.0 ? 255 : (int)(255.0 * r)),
                g < 0.0 ? 0 : (g > 1.0 ? 255 : (int)(255.0 * g)),
                b < 0.0 ? 0 : (b > 1.0 ? 255 : (int)(255.0 * b)));
    int top = lua_gettop(L);
    int status = lua_pcall(L, 0, LUA_MULTRET, 0);
    btui_set_attributes(*bt, BTUI_FG_NORMAL);
    if (status != LUA_OK)
        lua_error(L);
    return lua_gettop(L) - top;
}

static int Lbtui_withbg(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    if (lua_gettop(L) < 5) luaL_error(L, "Expected r,g,b values and a function");
    lua_Number r = luaL_checknumber(L, 2);
    lua_Number g = luaL_checknumber(L, 3);
    lua_Number b = luaL_checknumber(L, 4);
    btui_set_bg(*bt,
                r < 0.0 ? 0 : (r > 1.0 ? 255 : (int)(255.0 * r)),
                g < 0.0 ? 0 : (g > 1.0 ? 255 : (int)(255.0 * g)),
                b < 0.0 ? 0 : (b > 1.0 ? 255 : (int)(255.0 * b)));
    int top = lua_gettop(L);
    int status = lua_pcall(L, 0, LUA_MULTRET, 0);
    btui_set_attributes(*bt, BTUI_BG_NORMAL);
    if (status != LUA_OK)
        lua_error(L);
    return lua_gettop(L) - top;
}

static int Lbtui_linebox(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int x = (int)luaL_checkinteger(L, 2);
    int y = (int)luaL_checkinteger(L, 3);
    int w = (int)luaL_checkinteger(L, 4);
    int h = (int)luaL_checkinteger(L, 5);
    btui_draw_linebox(*bt, x, y, w, h);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_fillbox(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int x = (int)luaL_checkinteger(L, 2);
    int y = (int)luaL_checkinteger(L, 3);
    int w = (int)luaL_checkinteger(L, 4);
    int h = (int)luaL_checkinteger(L, 5);
    btui_fill_box(*bt, x, y, w, h);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_shadow(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int x = (int)luaL_checkinteger(L, 2);
    int y = (int)luaL_checkinteger(L, 3);
    int w = (int)luaL_checkinteger(L, 4);
    int h = (int)luaL_checkinteger(L, 5);
    btui_draw_shadow(*bt, x, y, w, h);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_scroll(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int firstline = (int)luaL_checkinteger(L, 2);
    int lastline = (int)luaL_checkinteger(L, 3);
    int scroll = (int)luaL_checkinteger(L, 4);
    btui_scroll(*bt, firstline, lastline, scroll);
    btui_flush(*bt);
    return 0;
}

static int Lbtui_setattributes(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int top = lua_gettop(L);
    lua_pushlightuserdata(L, (void*)&BTUI_ATTRIBUTES);
    int attr_table = lua_gettop(L);
    lua_gettable(L, LUA_REGISTRYINDEX);
    lua_Unsigned attrs = 0;
    for (int i = 2; i <= top; i++) {
        lua_pushvalue(L, i);
        lua_gettable(L, attr_table);
        if (lua_isnil(L, -1)) {
            const char *a = lua_tostring(L, i);
            luaL_error(L, "invalid attribute: %s", a);
        }
        attrs |= (lua_Unsigned)lua_tointeger(L, -1);
    }
    btui_set_attributes(*bt, attrs);
    return 0;
}

static int Lbtui_unsetattributes(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int top = lua_gettop(L);
    lua_pushlightuserdata(L, (void*)&BTUI_INVERSE_ATTRIBUTES);
    int attr_table = lua_gettop(L);
    lua_gettable(L, LUA_REGISTRYINDEX);
    lua_Unsigned attrs = 0;
    for (int i = 2; i <= top; i++) {
        lua_pushvalue(L, i);
        lua_gettable(L, attr_table);
        if (lua_isnil(L, -1)) {
            const char *a = lua_tostring(L, i);
            luaL_error(L, "invalid attribute: %s", a);
        }
        attrs |= (lua_Unsigned)lua_tointeger(L, -1);
    }
    btui_set_attributes(*bt, attrs);
    return 0;
}

static int Lbtui_suspend(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    btui_suspend(*bt);
    return 0;
}

static int Lbtui_withattributes(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    int top = lua_gettop(L);
    lua_pushcfunction(L, Lbtui_setattributes);
    for (int i = 1; i < top; i++) {
        lua_pushvalue(L, i);
    }
    lua_call(L, top - 1, 0);
    lua_pushvalue(L, top);
    int status = lua_pcall(L, 0, LUA_MULTRET, 0);
    int top2 = lua_gettop(L);
    lua_pushcfunction(L, Lbtui_unsetattributes);
    for (int i = 1; i < top; i++) {
        lua_pushvalue(L, i);
    }
    lua_call(L, top - 1, 0);
    if (status != LUA_OK)
        lua_error(L);
    return top2 - top;
}

static int Lbtui_width(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    lua_pushinteger(L, (*bt)->width);
    return 1;
}

static int Lbtui_height(lua_State *L)
{
    btui_t **bt = (btui_t**)lua_touserdata(L, 1);
    if (bt == NULL) luaL_error(L, "Not a BTUI object");
    lua_pushinteger(L, (*bt)->height);
    return 1;
}

static int Lbtui_wrap(lua_State *L)
{
    if (lua_gettop(L) < 1) luaL_error(L, "expected a callable object");
    btui_t **bt = (btui_t**)lua_newuserdata(L, sizeof(btui_t*));
    lua_pushlightuserdata(L, (void*)&BTUI_METATABLE);
    lua_gettable(L, LUA_REGISTRYINDEX);
    lua_setmetatable(L, -2);
    *bt = btui_create(BTUI_MODE_TUI);
    btui_move_cursor(*bt, 0, 0);
    btui_flush(*bt);
    int status = lua_pcall(L, 1, 0, 0);
    btui_disable(*bt);
    if (status != LUA_OK)
        lua_error(L);
    return 0;
}

static int Lbtui_tostring(lua_State *L)
{
    lua_pushliteral(L, "<BTUI>");
    return 1;
}

static struct {
    const char* name;
    lua_Unsigned code;
} btui_attributes[] =
{
    {"normal",                 BTUI_NORMAL},
    {"bold",                   BTUI_BOLD},
    {"faint",                  BTUI_FAINT},
    {"dim",                    BTUI_FAINT},
    {"italic",                 BTUI_ITALIC},
    {"underline",              BTUI_UNDERLINE},
    {"blink_slow",             BTUI_BLINK_SLOW},
    {"blink_fast",             BTUI_BLINK_FAST},
    {"reverse",                BTUI_REVERSE},
    {"conceal",                BTUI_CONCEAL},
    {"strikethrough",          BTUI_STRIKETHROUGH},
    {"fraktur",                BTUI_FRAKTUR},
    {"double_underline",       BTUI_DOUBLE_UNDERLINE},
    {"no_bold_or_faint",       BTUI_NO_BOLD_OR_FAINT},
    {"no_italic_or_fraktur",   BTUI_NO_ITALIC_OR_FRAKTUR},
    {"no_underline",           BTUI_NO_UNDERLINE},
    {"no_blink",               BTUI_NO_BLINK},
    {"no_reverse",             BTUI_NO_REVERSE},
    {"no_conceal",             BTUI_NO_CONCEAL},
    {"no_strikethrough",       BTUI_NO_STRIKETHROUGH},
    {"fg_black",               BTUI_FG_BLACK},
    {"fg_red",                 BTUI_FG_RED},
    {"fg_green",               BTUI_FG_GREEN},
    {"fg_yellow",              BTUI_FG_YELLOW},
    {"fg_blue",                BTUI_FG_BLUE},
    {"fg_magenta",             BTUI_FG_MAGENTA},
    {"fg_cyan",                BTUI_FG_CYAN},
    {"fg_white",               BTUI_FG_WHITE},
    {"fg_normal",              BTUI_FG_NORMAL},
    {"bg_black",               BTUI_BG_BLACK},
    {"bg_red",                 BTUI_BG_RED},
    {"bg_green",               BTUI_BG_GREEN},
    {"bg_yellow",              BTUI_BG_YELLOW},
    {"bg_blue",                BTUI_BG_BLUE},
    {"bg_magenta",             BTUI_BG_MAGENTA},
    {"bg_cyan",                BTUI_BG_CYAN},
    {"bg_white",               BTUI_BG_WHITE},
    {"bg_normal",              BTUI_BG_NORMAL},
    {"framed",                 BTUI_FRAMED},
    {"encircled",              BTUI_ENCIRCLED},
    {"overlined",              BTUI_OVERLINED},
    {"no_framed_or_encircled", BTUI_NO_FRAMED_OR_ENCIRCLED},
    {"no_overlined",           BTUI_NO_OVERLINED},
    {NULL,                     0}
};

static struct {
    const char* name;
    lua_Unsigned code;
} btui_inverse_attributes[] =
{
    {"normal",               BTUI_NORMAL},
    {"bold",                 BTUI_NO_BOLD_OR_FAINT},
    {"faint",                BTUI_NO_BOLD_OR_FAINT},
    {"dim",                  BTUI_NO_BOLD_OR_FAINT},
    {"italic",               BTUI_NO_ITALIC_OR_FRAKTUR},
    {"underline",            BTUI_NO_UNDERLINE},
    {"blink_slow",           BTUI_NO_BLINK},
    {"blink_fast",           BTUI_NO_BLINK},
    {"reverse",              BTUI_NO_REVERSE},
    {"conceal",              BTUI_NO_CONCEAL},
    {"strikethrough",        BTUI_NO_STRIKETHROUGH},
    {"fraktur",              BTUI_NO_ITALIC_OR_FRAKTUR},
    {"double_underline",     BTUI_NO_UNDERLINE},
    {"fg_black",             BTUI_FG_NORMAL},
    {"fg_red",               BTUI_FG_NORMAL},
    {"fg_green",             BTUI_FG_NORMAL},
    {"fg_yellow",            BTUI_FG_NORMAL},
    {"fg_blue",              BTUI_FG_NORMAL},
    {"fg_magenta",           BTUI_FG_NORMAL},
    {"fg_cyan",              BTUI_FG_NORMAL},
    {"fg_white",             BTUI_FG_NORMAL},
    {"fg_normal",            BTUI_FG_NORMAL},
    {"bg_black",             BTUI_BG_NORMAL},
    {"bg_red",               BTUI_BG_NORMAL},
    {"bg_green",             BTUI_BG_NORMAL},
    {"bg_yellow",            BTUI_BG_NORMAL},
    {"bg_blue",              BTUI_BG_NORMAL},
    {"bg_magenta",           BTUI_BG_NORMAL},
    {"bg_cyan",              BTUI_BG_NORMAL},
    {"bg_white",             BTUI_BG_NORMAL},
    {"bg_normal",            BTUI_BG_NORMAL},
    {"framed",               BTUI_NO_FRAMED_OR_ENCIRCLED},
    {"encircled",            BTUI_NO_FRAMED_OR_ENCIRCLED},
    {"overlined",            BTUI_NO_OVERLINED},
    {NULL,                   0}
};

static const luaL_Reg Rclass_metamethods[] =
{
    {"__tostring",      Lbtui_tostring},
    {"batch",           Lbtui_batch},
    {"batch_begin",     Lbtui_batch_begin},
    {"batch_end",       Lbtui_batch_end},
    {"clear",           Lbtui_clear},
    {"disable",         Lbtui_disable},
    {"enable",          Lbtui_enable},
    {"fillbox",         Lbtui_fillbox},
    {"flush",           Lbtui_flush},
    {"getkey",          Lbtui_getkey},
    {"height",          Lbtui_height},
    {"hidecursor",      Lbtui_hidecursor},
    {"linebox",         Lbtui_linebox},
    {"move",            Lbtui_move},
    {"scroll",          Lbtui_scroll},
    {"setattributes",   Lbtui_setattributes},
    {"setcursor",       Lbtui_setcursor},
    {"setmode",         Lbtui_setmode},
    {"shadow",          Lbtui_shadow},
    {"showcursor",      Lbtui_showcursor},
    {"suspend",         Lbtui_suspend},
    {"unsetattributes", Lbtui_unsetattributes},
    {"width",           Lbtui_width},
    {"withattributes",  Lbtui_withattributes},
    {"withbg",          Lbtui_withbg},
    {"withdisabled",    Lbtui_withdisabled},
    {"withfg",          Lbtui_withfg},
    {"write",           Lbtui_write},
    {NULL,              NULL}
};

LUALIB_API int luaopen_btui(lua_State *L)
{
    // Set up attributes
    lua_pushlightuserdata(L, (void*)&BTUI_ATTRIBUTES);
    lua_createtable(L, 0, 50);
    for (int i = 0; btui_attributes[i].name; i++) {
        lua_pushinteger(L, (lua_Integer)btui_attributes[i].code);
        lua_setfield(L, -2, btui_attributes[i].name);
    }
    lua_settable(L, LUA_REGISTRYINDEX);

    // Inverse attributes
    lua_pushlightuserdata(L, (void*)&BTUI_INVERSE_ATTRIBUTES);
    lua_createtable(L, 0, 50);
    for (int i = 0; btui_inverse_attributes[i].name; i++) {
        lua_pushinteger(L, (lua_Integer)btui_inverse_attributes[i].code);
        lua_setfield(L, -2, btui_inverse_attributes[i].name);
    }
    lua_settable(L, LUA_REGISTRYINDEX);

    // Set up BTUI metatable
    lua_pushlightuserdata(L, (void*)&BTUI_METATABLE);
    lua_createtable(L, 0, 16);
    luaL_register(L, NULL, Rclass_metamethods);
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    lua_settable(L, LUA_REGISTRYINDEX);

    lua_pushcfunction(L, Lbtui_wrap);
    return 1;
}