/* termfx.c * * provide simple terminal interface for lua * * Gunnar Zötl , 2014-2015 * Released under the terms of the MIT license. See file LICENSE for details. */ #include #include #include #include #include #include #include "lua.h" #include "lauxlib.h" #include "mini_utf8.h" #include "termbox.h" #include "tbutils.h" #include "termfx.h" /* userdata for a termbox cell */ typedef struct tb_cell TfxCell; /* userdata for a termfx offscreen buffer */ typedef struct { int w, h; uint16_t fg, bg; struct tb_cell *buf; } TfxBuffer; static const int top_left_coord = 1; static uint16_t default_fg, default_bg; static int initialized = 0; static uint64_t mstimer_tm0 = 0; /* helper: try to get a char from the stack at index. If the value there * is a number, return it. If the value is a string of length 1, return * the value of the first char. If it is a string of length >1, try to * read it as an utf8 encoded char. */ static uint32_t _tfx_getchar(lua_State *L, int index) { uint32_t res = 0; int t = lua_type(L, index); if (t == LUA_TNUMBER) res = (uint32_t) lua_tointeger(L, index); else if (t == LUA_TSTRING) { const char* str = lua_tostring(L, index); int l = strlen(str); if (l == 1) res = *str; else if (l > 1) { res = mini_utf8_decode(&str); if (*str != 0) return luaL_argerror(L, index, "invalid char"); } } else { return luaL_argerror(L, index, "number or string"); } return res; } /* helper: as above, but if there's nil or nothing on the stack at the * index, return the default value. */ static uint32_t _tfx_optchar(lua_State *L, int index, uint32_t dfl) { if (!lua_isnoneornil(L, index)) return _tfx_getchar(L, index); return dfl; } /* helper: check whether the value of the given index on the lua stack * is a userdata of the given type. Return 1 for yes, 0 for no. */ static int _tfx_isUserdataOfType(lua_State *L, int index, const char* type) { if (lua_isuserdata(L, index)) { if (lua_getmetatable(L, index)) { luaL_getmetatable(L, type); if (lua_rawequal(L, -1, -2)) { lua_pop(L, 2); return 1; } lua_pop(L, 2); } } return 0; } /* helper: return a millisecond timer, which is 0 at the load time of * the library, so that the result will fit into an uint32_t for some time * (a little more than 49 days) */ static uint32_t _tfx_getMsTimer(void) { struct timeval tv; gettimeofday(&tv, NULL); uint64_t tm = tv.tv_sec * 1000 + tv.tv_usec / 1000 - mstimer_tm0; if (tm > INT_MAX) { mstimer_tm0 += INT_MAX; tm -= INT_MAX; } return (uint32_t) tm; } /*** TfxCell Userdata handling ***/ /* tfx_isCell * * If the value at the given acceptable index is a full userdata of the * type TFXCELL, then return 1, else return 0. * * Arguments: * L Lua State * index stack index where the userdata is expected */ static int tfx_isCell(lua_State *L, int index) { return _tfx_isUserdataOfType(L, index, TFXCELL); } /* tfx_toCell * * If the value at the given acceptable index is a full userdata, returns * its block address. Otherwise, returns NULL. * * Arguments: * L Lua State * index stack index where the userdata is expected */ static TfxCell* tfx_toCell(lua_State *L, int index) { TfxCell *tfxcell = (TfxCell*) lua_touserdata(L, index); return tfxcell; } /* tfx_checkCell * * Checks whether the function argument narg is a userdata of the type * TFXCELL. If so, returns its block address, else throw an error. * * Arguments: * L Lua State * index stack index where the userdata is expected */ static TfxCell* tfx_checkCell(lua_State *L, int index) { TfxCell *tfxcell = (TfxCell*) luaL_checkudata(L, index, TFXCELL); return tfxcell; } /* tfx_pushCell * * create a new, empty TfxCell userdata and push it to the stack. * * Arguments: * L Lua state */ static TfxCell* tfx_pushCell(lua_State *L) { TfxCell *tfxcell = (TfxCell*) lua_newuserdata(L, sizeof(TfxCell)); luaL_getmetatable(L, TFXCELL); lua_setmetatable(L, -2); return tfxcell; } /* tfx__toStringCell * * __tostring metamethod for the TfxCell userdata. * Returns a string representation of the TfxCell * * Arguments: * L Lua State * * Lua Stack: * 1 TfxCell userdata * * Lua Returns: * +1 the string representation of the TfxCell userdata */ static int tfx__tostringCell(lua_State *L) { TfxCell *tfxcell = (TfxCell*) lua_touserdata(L, 1); lua_pushfstring(L, "%s (%p)", TFXCELL, tfxcell); return 1; } /* tfx__indexCell * * __index metamethod for the TfxCell userdata. * Returns a the value accessible at the index which is on top of the * stack, or nil of there is no such value * * Arguments: * L Lua State * * Lua Stack: * 1 TfxCell userdata * 2 index to access * * Lua Returns: * +1 value accessible through index */ static int tfx__indexCell(lua_State *L) { TfxCell *tfxcell = tfx_checkCell(L, 1); const char* what = luaL_checkstring(L, 2); if (!strcmp(what, "ch")) lua_pushinteger(L, tfxcell->ch); else if (!strcmp(what, "fg")) lua_pushinteger(L, tfxcell->fg); else if (!strcmp(what, "bg")) lua_pushinteger(L, tfxcell->bg); else lua_pushnil(L); return 1; } /* tfx__newindexCell * * __newindex metamethod for the TfxCell userdata. * Sets a the value accessible at the index which is on the stack. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxCell userdata * 2 index to set * 3 value to set * * Lua Returns: * - */ static int tfx__newindexCell(lua_State *L) { TfxCell *tfxcell = tfx_checkCell(L, 1); const char* what = luaL_checkstring(L, 2); uint32_t val = (uint32_t) luaL_checkinteger(L, 3); if (!strcmp(what, "ch")) tfxcell->ch = val < ' ' ? ' ' : val; else if (!strcmp(what, "fg")) tfxcell->fg = val; else if (!strcmp(what, "bg")) tfxcell->bg = val; return 0; } /* tfx_newCell * * create a new TfxCell object, initialize it, put it into a userdata and * return it to the user. * * Arguments: * L Lua State * * Lua Stack: * +1 (opt) char to store, defaults to ' ' * +2 (opt) forgeround color, defaults to tfx default foreground color * +3 (opt) background color, defaults to tfx default background color * * Lua Returns: * +1 the TfxCell userdata */ static int tfx_newCell(lua_State *L) { maxargs(L, 3); uint32_t ch = _tfx_optchar(L, 1, ' '); uint16_t fg = (uint16_t) luaL_optinteger(L, 2, default_fg) & 0xFFFF; uint16_t bg = (uint16_t) luaL_optinteger(L, 3, default_bg) & 0xFFFF; TfxCell *tfxcell = tfx_pushCell(L); tfxcell->ch = ch < ' ' ? ' ' : ch; tfxcell->fg = fg; tfxcell->bg = bg; return 1; } /* metamethods for the TfxCell userdata */ static const luaL_Reg tfx_CellMeta[] = { {"__tostring", tfx__tostringCell}, {"__index", tfx__indexCell}, {"__newindex", tfx__newindexCell}, {NULL, NULL} }; /*** TfxBuffer Userdata handling ***/ /* tfx_isBuffer * * If the value at the given acceptable index is a full userdata of the * type TFXBUFFER, then return 1, else return 0. * * Arguments: * L Lua State * index stack index where the userdata is expected */ static int tfx_isBuffer(lua_State *L, int index) { return _tfx_isUserdataOfType(L, index, TFXBUFFER); } /* tfx_checkBuffer * * Checks whether the function argument narg is a userdata of the type * TFXBUFFER. If so, returns its block address, else throw an error. * * Arguments: * L Lua State * index stack index where the userdata is expected */ static TfxBuffer* tfx_checkBuffer(lua_State *L, int index) { TfxBuffer *tfxbuf = (TfxBuffer*) luaL_checkudata(L, index, TFXBUFFER); return tfxbuf; } /* tfx_pushBuffer * * create a new, empty TfxBuffer userdata and push it to the stack. * * Arguments: * L Lua state */ static TfxBuffer* tfx_pushBuffer(lua_State *L) { TfxBuffer *tfxbuf = (TfxBuffer*) lua_newuserdata(L, sizeof(TfxBuffer)); luaL_getmetatable(L, TFXBUFFER); lua_setmetatable(L, -2); return tfxbuf; } /*** Housekeeping metamethods ***/ /* tfx__gcBuffer * * __gc metamethod for the TfxBuffer userdata. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata */ static int tfx__gcBuffer(lua_State *L) { TfxBuffer *tfxbuf = (TfxBuffer*) lua_touserdata(L, 1); if (tfxbuf->buf) free(tfxbuf->buf); return 0; } /* tfx__tostringBuffer * * __tostring metamethod for the TfxBuffer userdata. * Returns a string representation of the TfxBuffer * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * * Lua Returns: * +1 the string representation of the TfxCell userdata */ static int tfx__tostringBuffer(lua_State *L) { TfxBuffer *tfxbuf = (TfxBuffer*) lua_touserdata(L, 1); lua_pushfstring(L, "%s (%p)", TFXBUFFER, tfxbuf); return 1; } /* metamethods for the TfxBuffer userdata */ static const luaL_Reg tfx_BufferMeta[] = { {"__gc", tfx__gcBuffer}, {"__tostring", tfx__tostringBuffer}, {NULL, NULL} }; /* tfx_newBuffer * * create a new TfxBuffer object, initialize it, put it into a userdata * and return it to the user. * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * +1 the TfxBuffer userdata */ static int tfx_newBuffer(lua_State *L) { maxargs(L, 2); int w = (int) luaL_checkinteger(L, 1); int h = (int) luaL_checkinteger(L, 2); if (w < 1 || h < 1) return luaL_error(L, "buffer dimensions must be >=1"); TfxBuffer *tfxbuf = tfx_pushBuffer(L); tfxbuf->buf = calloc(w * h, sizeof(struct tb_cell)); tfxbuf->w = w; tfxbuf->h = h; tfxbuf->fg = default_fg; tfxbuf->bg = default_bg; return 1; } /* tfx_bufAttributes * * sets or gets default foreground and background attributes on a buffer. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * 2 (opt) foreground attributes * 3 (opt) background attributes * * Lua Returns: * +1 buffer default foreground attribute * +2 buffer default background attribute */ static int tfx_bufAttributes(lua_State *L) { maxargs(L, 3); TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); if (lua_gettop(L) > 1) { tfxbuf->fg = (uint16_t) luaL_optinteger(L, 2, tfxbuf->fg) & 0xFFFF; tfxbuf->bg = (uint16_t) luaL_optinteger(L, 3, tfxbuf->bg) & 0xFFFF; } lua_pushinteger(L, tfxbuf->fg); lua_pushinteger(L, tfxbuf->bg); return 2; } /* tfx_bufClear * * clears a buffer to the default foreground and background attributes. * If the optional foreground and background attribute arguments are * given, these will be set using tfx_bufSetAttributes before clearing. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * 2 (opt) foreground attributes, defaults to buffer default foreground * 3 (opt) background attributes, defaults to buffer default background * * Lua Returns: * - */ static int tfx_bufClear(lua_State *L) { if (lua_gettop(L) > 1) tfx_bufAttributes(L); TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int c; for (c = 0; c < tfxbuf->w * tfxbuf->h; ++c) { TfxCell *cell = &tfxbuf->buf[c]; cell->fg = tfxbuf->fg; cell->bg = tfxbuf->bg; cell->ch = ' '; } return 0; } /* _tfx_bufChangeCell * * analog to tb_changecell() for buffers */ static int _tfx_bufChangeCell(TfxBuffer *tfxbuf, int x, int y, TfxCell *c) { if (x > tfxbuf->w || y > tfxbuf->h || x < 0 || y < 0) return 0; TfxCell *cell = &tfxbuf->buf[y * tfxbuf->w + x]; *cell = *c; return 1; } /* tfx_bufSetcell * * sets a cell in a buffer. Either uses the default values for foreground * and background, or the values passed on the lua stack. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * 2 x coordinate of cell * 3 y coordinate of cell * either * 4 TfxCell * or * 4 (opt) char, defaults to ' ' * 5 (opt) foreground attributes, defaults to buffer default foreground * 6 (opt) background attributes, defaults to buffer default background * * Lua Returns: * - */ static int tfx_bufSetcell(lua_State *L) { TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int x = (int) luaL_checkinteger(L, 2) - top_left_coord; int y = (int) luaL_checkinteger(L, 3) - top_left_coord; uint32_t ch = ' '; uint16_t fg = TB_WHITE, bg = TB_BLACK; if (tfx_isCell(L, 4)) { maxargs(L, 4); TfxCell *tfxcell = tfx_toCell(L, 4); ch = tfxcell->ch; fg = tfxcell->fg; bg = tfxcell->bg; } else { maxargs(L, 6); ch = _tfx_optchar(L, 4, ' '); fg = (uint16_t) luaL_optinteger(L, 5, tfxbuf->fg) & 0xFFFF; bg = (uint16_t) luaL_optinteger(L, 6, tfxbuf->bg) & 0xFFFF; } TfxCell cell; cell.ch = ch < '?' ? ' ' : ch; cell.fg = fg; cell.bg = bg; _tfx_bufChangeCell(tfxbuf, x, y, &cell); return 0; } /* tfx_bufGetCell * * gets data from a cell in a buffer. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * 2 x coordinate of cell * 3 y coordinate of cell * * Lua Returns: * a TfxCell with the data from the read cell */ int tfx_bufGetCell(lua_State *L) { TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int x = (int) luaL_checkinteger(L, 2) - top_left_coord; int y = (int) luaL_checkinteger(L, 3) - top_left_coord; if (x < 0 || x >= tfxbuf->w || y < 0 || y >= tfxbuf->h) { lua_pushnil(L); return 1; } TfxCell *tfxcell = tfx_pushCell(L); *tfxcell = CELL(tfxbuf->buf, tfxbuf->w, x, y); return 1; } /* tfx_bufBlit * * blits the contents of a buffer to another buffer. Just a modified * copy of my improved tb_blit() routine. * * Arguments: * L Lua State * * Lua Stack: * 1 destination TfxBuffer * 2 x coordinate of cell * 3 y coordinate of cell * 4 source TfxBuffer * * Lua Returns: * - */ static int tfx_bufBlit(lua_State *L) { TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int x = (int) luaL_checkinteger(L, 2) - top_left_coord; int y = (int) luaL_checkinteger(L, 3) - top_left_coord; TfxBuffer *other = tfx_checkBuffer(L, 4); tbu_blitbuffer(tfxbuf->buf, tfxbuf->w, tfxbuf->h, x, y, other->buf, other->w, other->h); return 0; } /* tfx_bufRect * * draws a rectangle. Either uses the default values for foreground and * background, or the values passed on the lua stack. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * 2 x coordinate of rect * 3 y coordinate of rect * 4 width of rect * 5 height of rect * either * 6 TfxCell * or * 6 (opt) char, defaults to ' ' * 7 (opt) foreground attributes, defaults to buffer default foreground * 8 (opt) background attributes, defaults to buffer default background * * Lua Returns: * false if the rectangle was entirely outside of the terminal, true if not */ static int tfx_bufRect(lua_State *L) { TfxCell cel, *celp = &cel; TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int x = (int) luaL_checkinteger(L, 2) - top_left_coord; int y = (int) luaL_checkinteger(L, 3) - top_left_coord; int w = (int) luaL_checkinteger(L, 4); int h = (int) luaL_checkinteger(L, 5); if (tfx_isCell(L, 6)) { maxargs(L, 6); celp = tfx_toCell(L, 6); } else { maxargs(L, 8); cel.ch = _tfx_optchar(L, 6, 0); cel.fg = (uint16_t) luaL_optinteger(L, 7, default_fg) & 0xFFFF; cel.bg = (uint16_t) luaL_optinteger(L, 8, default_bg) & 0xFFFF; } tbu_fillbufferregion(tfxbuf->buf, tfxbuf->w, tfxbuf->h, x, y, w, h, celp); return 0; } /* tfx_bufCopyRegion * * copies a rectangular region of a buffer to another place * * Arguments: * L Lua State * * Lua Stack * 1 TfxBuffer userdata * 2 target x coordinate for copy * 3 target y coordinate for copy * 4 source x coordinate * 5 source y coordinate * 6 width of rectangle to copy * 7 height of rectangle to copy * * Lua Returns: * - */ static int tfx_bufCopyRegion(lua_State *L) { TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int tx = (int) luaL_checkinteger(L, 2) - top_left_coord; int ty = (int) luaL_checkinteger(L, 3) - top_left_coord; int x = (int) luaL_checkinteger(L, 4) - top_left_coord; int y = (int) luaL_checkinteger(L, 5) - top_left_coord; int w = (int) luaL_checkinteger(L, 6); int h = (int) luaL_checkinteger(L, 7); tbu_copybufferregion(tfxbuf->buf, tfxbuf->w, tfxbuf->h, tx, ty, x, y, w, h); return 0; } /* tfx_bufScrollRegion * * scrolls a rectangular region of a buffer * * Arguments: * L Lua State * * Lua Stack * 1 TfxBuffer userdata * 2 x coordinate of rectangle to scroll * 3 y coordinate of rectangle to scroll * 4 width of rectangle to scroll * 5 height of rectangle to scroll * 6 (opt) x scroll direction (-1, 0, 1), defaults to 0 * 7 (opt) y scroll direction (-1, 0, 1), defaults to 0 * either * 8 TfxCell * or * 8 (opt) char, defaults to ' ' * 9 (opt) foreground attributes, defaults to buffer default foreground * 10 (opt) background attributes, defaults to buffer default background * * Lua Returns: * - * * Note: * arguments 6 and 7 (scroll directions) are ints, but only their sign is * used. Scrolling is always by at most 1 cell. */ int tfx_bufScrollRegion(lua_State *L) { if (!initialized) return 0; TfxCell cel, *celp = &cel; TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); int x = (int) luaL_checkinteger(L, 2) - top_left_coord; int y = (int) luaL_checkinteger(L, 3) - top_left_coord; int w = (int) luaL_checkinteger(L, 4); int h = (int) luaL_checkinteger(L, 5); int sx = (int) luaL_optinteger(L, 6, 0); int sy = (int) luaL_optinteger(L, 7, 0); if (tfx_isCell(L, 8)) { maxargs(L, 8); celp = tfx_toCell(L, 8); } else { maxargs(L, 10); cel.ch = _tfx_optchar(L, 8, 0); cel.fg = (uint16_t) luaL_optinteger(L, 9, default_fg) & 0xFFFF; cel.bg = (uint16_t) luaL_optinteger(L, 10, default_bg) & 0xFFFF; } tbu_scrollbufferregion(tfxbuf->buf, tfxbuf->w, tfxbuf->h, x, y, w, h, sx, sy, celp); return 0; } /* tfx_bufWidth * * returns the width of the buffer * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * * Lua Returns: * +1 width of buffer */ static int tfx_bufWidth(lua_State *L) { maxargs(L, 1); TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); lua_pushinteger(L, tfxbuf->w); return 1; } /* tfx_bufHeight * * returns the height of the buffer * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * * Lua Returns: * +1 height of buffer */ static int tfx_bufHeight(lua_State *L) { maxargs(L, 1); TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); lua_pushinteger(L, tfxbuf->h); return 1; } /* tfx_bufSize * * returns width and height of the buffer * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * * Lua Returns: * +1 width of buffer * +2 height of buffer */ static int tfx_bufSize(lua_State *L) { maxargs(L, 1); TfxBuffer *tfxbuf = tfx_checkBuffer(L, 1); lua_pushinteger(L, tfxbuf->w); lua_pushinteger(L, tfxbuf->h); return 2; } /*** termfx stuff ***/ /* tfx_init * * initialize TermFX * * Arguments: * L Lua State * * Lua Stack: * 1 (opt) boolean value, if true, then all coordinates will be * 0-based, otherwise they will be 1-based. * * Lua Returns: * - */ static int tfx_init(lua_State *L) { maxargs(L, 0); default_fg = TB_WHITE; default_bg = TB_BLACK; int status = tb_init(); if (status < 0) { switch(status) { case TB_EUNSUPPORTED_TERMINAL: return luaL_error(L, "unsupported terminal"); case TB_EFAILED_TO_OPEN_TTY: return luaL_error(L, "failed to open tty"); case TB_EPIPE_TRAP_ERROR: return luaL_error(L, "sigpipe trap"); default: return luaL_error(L, "unknown error"); } } else initialized = 1; return 0; } /* helper: shutdown termfx. Also used for atexit. */ static void _tfx_doShutdown(void) { if (initialized) tb_shutdown(); initialized = 0; } /* tfx_shutdown * * shutdown TermFX * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * - */ static int tfx_shutdown(lua_State *L) { maxargs(L, 0); _tfx_doShutdown(); return 0; } /* tfx_width * * returns width of terminal * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * +1 width of terminal, or nil if the terminal is not initialized. */ static int tfx_width(lua_State *L) { maxargs(L, 0); int w = tb_width(); if (w >= 0) lua_pushinteger(L, w); else lua_pushnil(L); return 1; } /* tfx_height * * returns height of terminal * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * +1 height of terminal, or nil if the terminal is not initialized. */ static int tfx_height(lua_State *L) { maxargs(L, 0); int h = tb_height(); if (h >= 0) lua_pushinteger(L, h); else lua_pushnil(L); return 1; } /* tfx_size * * returns width and height of terminal * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * +1 width of terminal, or nil if the terminal is not initialized. * +2 height of terminal, or nil if the terminal is not initialized. */ static int tfx_size(lua_State *L) { maxargs(L, 0); int w = tb_width(), h = tb_height(); if (w >= 0) { lua_pushinteger(L, w); lua_pushinteger(L, h); } else { lua_pushnil(L); lua_pushnil(L); } return 2; } /* tfx_attributes * * sets and gets default foreground and background attributes for terminal * operations * * Arguments: * L Lua State * * Lua Stack: * 1 (opt) foreground attribute * 2 (opt) background attribute * * Lua Returns: * +1 default foreground attribute, or nothing if terminal is not initialized * +2 default background attribute, or nothing if terminal is not initialized */ static int tfx_attributes(lua_State *L) { maxargs(L, 2); if (!initialized) return 0; if (lua_gettop(L) > 0) { default_fg = (uint16_t) luaL_optinteger(L, 1, default_fg) & 0xFFFF; default_bg = (uint16_t) luaL_optinteger(L, 2, default_bg) & 0xFFFF; tb_set_clear_attributes(default_fg, default_bg); } lua_pushinteger(L, default_fg); lua_pushinteger(L, default_bg); return 2; } /* tfx_clear * * clears the terminal to the default foreground and background attributes. * If the optional foreground and background attribute arguments are * given, these will be set using tfx_bufSetAttributes before clearing. * * Arguments: * L Lua State * * Lua Stack: * 1 TfxBuffer userdata * 2 (opt) foreground attributes, defaults to buffer default foreground * 3 (opt) background attributes, defaults to buffer default background * * Lua Returns: * - */ static int tfx_clear(lua_State *L) { if (lua_gettop(L) != 0) tfx_attributes(L); if (initialized) tb_clear(); return 0; } /* tfx_present * * update the terminal with the data from the back buffer * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * - */ static int tfx_present(lua_State *L) { maxargs(L, 0); if (initialized) tb_present(); return 0; } /* tfx_setCursor * * set cursor position * * Arguments: * L Lua State * * Lua Stack: * 1 x coordinate * 2 y coordinate * * Lua Returns: * - */ static int tfx_setCursor(lua_State *L) { maxargs(L, 2); int x = (int) luaL_checkinteger(L, 1) - top_left_coord; int y = (int) luaL_checkinteger(L, 2) - top_left_coord; if (initialized) tb_set_cursor(x, y); return 0; } /* tfx_hideCursor * * hide cursor * * Arguments: * L Lua State * * Lua Stack: * - * * Lua Returns: * - */ static int tfx_hideCursor(lua_State *L) { maxargs(L, 0); if (initialized) tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR); return 0; } /* tfx_setcell * * sets a cell on the terminal. Either uses the default values for foreground * and background, or the values passed on the lua stack. * * Arguments: * L Lua State * * Lua Stack: * 1 x coordinate of cell * 2 y coordinate of cell * either * 3 TfxCell * or * 3 (opt) char, defaults to ' ' * 4 (opt) foreground attributes, defaults to buffer default foreground * 5 (opt) background attributes, defaults to buffer default background * * Lua Returns: * - */ static int tfx_setCell(lua_State *L) { int x = (int) luaL_checkinteger(L, 1) - top_left_coord; int y = (int) luaL_checkinteger(L, 2) - top_left_coord; if (tfx_isCell(L, 3)) { maxargs(L, 3); const TfxCell *tfxcell = tfx_checkCell(L, 3); if (initialized) tb_put_cell(x, y, tfxcell); } else { maxargs(L, 5); uint32_t ch = _tfx_optchar(L, 3, ' '); uint16_t fg = (uint16_t) luaL_optinteger(L, 4, default_fg) & 0xFFFF; uint16_t bg = (uint16_t) luaL_optinteger(L, 5, default_bg) & 0xFFFF; if (ch < ' ') ch = ' '; if (initialized) tb_change_cell(x, y, ch, fg, bg); } return 0; } /* tfx_getCell * * gets data from a cell on the terminal * * Arguments: * L Lua State * * Lua Stack: * 1 x coordinate of cell * 2 y coordinate of cell * * Lua Returns: * a TfxCell with the data from the read cell */ int tfx_getCell(lua_State *L) { int x = (int) luaL_checkinteger(L, 1) - top_left_coord; int y = (int) luaL_checkinteger(L, 2) - top_left_coord; if (x < 0 || x >= tb_width() || y < 0 || y >= tb_height()) { lua_pushnil(L); return 1; } TfxCell *tfxcell = tfx_pushCell(L); *tfxcell = CELL(tb_cell_buffer(), tb_width(), x, y); return 1; } /* tfx_blit * * blits the contents of a buffer to the terminal. * * Arguments: * L Lua State * * Lua Stack: * 1 x coordinate of cell * 2 y coordinate of cell * 3 TfxBuffer * * Lua Returns: * - */ static int tfx_blit(lua_State *L) { maxargs(L, 3); int x = (int) luaL_checkinteger(L, 1) - top_left_coord; int y = (int) luaL_checkinteger(L, 2) - top_left_coord; TfxBuffer *tfxbuf = tfx_checkBuffer(L, 3); if (initialized) tbu_blit(x, y, tfxbuf->buf, tfxbuf->w, tfxbuf->h); return 0; } /* tfx_rect * * draws a rectangle. Either uses the default values for foreground and * background, or the values passed on the lua stack. * * Arguments: * L Lua State * * Lua Stack: * 1 x coordinate of rect * 2 y coordinate of rect * 3 width of rect * 4 height of rect * either * 5 TfxCell * or * 5 (opt) char, defaults to ' ' * 6 (opt) foreground attributes, defaults to buffer default foreground * 7 (opt) background attributes, defaults to buffer default background * * Lua Returns: * false if the rectangle was entirely outside of the terminal, true if not */ static int tfx_rect(lua_State *L) { if (!initialized) return 0; TfxCell cel, *celp = &cel; int x = (int) luaL_checkinteger(L, 1) - top_left_coord; int y = (int) luaL_checkinteger(L, 2) - top_left_coord; int w = (int) luaL_checkinteger(L, 3); int h = (int) luaL_checkinteger(L, 4); if (tfx_isCell(L, 5)) { maxargs(L, 5); celp = tfx_toCell(L, 5); } else { maxargs(L, 7); cel.ch = _tfx_optchar(L, 5, 0); cel.fg = (uint16_t) luaL_optinteger(L, 6, default_fg) & 0xFFFF; cel.bg = (uint16_t) luaL_optinteger(L, 7, default_bg) & 0xFFFF; } tbu_fillregion(x, y, w, h, celp); return 0; } /* tfx_copyRegion * * copies a rectangular region to another place * * Arguments: * L Lua State * * Lua Stack * 1 target x coordinate for copy * 2 target y coordinate for copy * 3 source x coordinate * 4 source y coordinate * 5 width of rectangle to copy * 6 height of rectangle to copy * * Lua Returns: * - */ int tfx_copyRegion(lua_State *L) { if (!initialized) return 0; int tx = (int) luaL_checkinteger(L, 1) - top_left_coord; int ty = (int) luaL_checkinteger(L, 2) - top_left_coord; int x = (int) luaL_checkinteger(L, 3) - top_left_coord; int y = (int) luaL_checkinteger(L, 4) - top_left_coord; int w = (int) luaL_checkinteger(L, 5); int h = (int) luaL_checkinteger(L, 6); tbu_copyregion(tx, ty, x, y, w, h); return 0; } /* tfx_scrollRegion * * scrolls a rectangular region * * Arguments: * L Lua State * * Lua Stack * 1 x coordinate of rectangle to scroll * 2 y coordinate of rectangle to scroll * 3 width of rectangle to scroll * 4 height of rectangle to scroll * 5 (opt) x scroll direction (-1, 0, 1), defaults to 0 * 6 (opt) y scroll direction (-1, 0, 1), defaults to 0 * either * 7 TfxCell * or * 7 (opt) char, defaults to ' ' * 8 (opt) foreground attributes, defaults to buffer default foreground * 9 (opt) background attributes, defaults to buffer default background * * Lua Returns: * - * * Note: * arguments 5 and 6 (scroll directions) are ints, but only their sign is * used. Scrolling is always by at most 1 cell. */ int tfx_scrollRegion(lua_State *L) { if (!initialized) return 0; TfxCell cel, *celp = &cel; int x = (int) luaL_checkinteger(L, 1) - top_left_coord; int y = (int) luaL_checkinteger(L, 2) - top_left_coord; int w = (int) luaL_checkinteger(L, 3); int h = (int) luaL_checkinteger(L, 4); int sx = (int) luaL_optinteger(L, 5, 0); int sy = (int) luaL_optinteger(L, 6, 0); if (tfx_isCell(L, 7)) { maxargs(L, 7); celp = tfx_toCell(L, 7); } else { maxargs(L, 9); cel.ch = _tfx_optchar(L, 7, 0); cel.fg = (uint16_t) luaL_optinteger(L, 8, default_fg) & 0xFFFF; cel.bg = (uint16_t) luaL_optinteger(L, 9, default_bg) & 0xFFFF; } tbu_scrollregion(x, y, w, h, sx, sy, celp); return 0; } /* tfx_printAt * * prints some text to the top left position at x, y. Is used as both the * function termfx.printat and the TfxBuffer printat method, depending on * the first argument. * * Arguments: * L Lua State * * Lua Stack: * (1 TfxBuffer) * +1 x coordinate of text * +2 y coordinate of text * +3 text (string or table of chars) * +4 (opt) maximum width to print * * Lua Returns: * - */ static int tfx_printAt(lua_State *L) { if (!initialized) return 0; TfxBuffer *tfxbuf = NULL; int fw, start = 1; uint16_t dfg = default_fg, dbg = default_bg; if (tfx_isBuffer(L, 1)) { tfxbuf = tfx_checkBuffer(L, 1); fw = tfxbuf->w; dfg = tfxbuf->fg; dbg = tfxbuf->bg; start = 2; } else { fw = tb_width(); } int x = (int) luaL_checkinteger(L, start) - top_left_coord; int y = (int) luaL_checkinteger(L, start + 1) - top_left_coord; int pw = -1; if (lua_gettop(L) == start + 3) pw = (int) luaL_checkinteger(L, start + 3); if (lua_type(L, start + 2) == LUA_TTABLE) { int i = 1; int w = 0; if (pw < 0) { pw = lua_rawlen(L, start + 2); } lua_rawgeti(L, start + 2, i); while (!lua_isnil(L, -1) && w < pw && x + w < fw) { TfxCell *c = tfx_toCell(L, -1); if (c) { if (tfxbuf) _tfx_bufChangeCell(tfxbuf, x + w, y, c); else tb_put_cell(x + w, y, c); } ++w; ++i; lua_rawgeti(L, start + 2, i); } } else if (!lua_isnil(L, start + 2)) { const char* str = luaL_checkstring(L, start + 2); int isutf8 = mini_utf8_check_encoding(str) == 0; int w = 0; if (pw < 0) { pw = isutf8 ? mini_utf8_strlen(str) : strlen(str); } struct tb_cell c; c.fg = dfg; c.bg = dbg; c.ch = isutf8 ? mini_utf8_decode(&str) : *str++; if (c.ch < ' ' && c.ch > 0) c.ch = ' '; for (w = 0; c.ch && (w < pw) && (x + w < fw); ++w) { if (tfxbuf) _tfx_bufChangeCell(tfxbuf, x + w, y, &c); else tb_put_cell(x + w, y, &c); c.ch = isutf8 ? mini_utf8_decode(&str) : *str++; if (c.ch < ' ' && c.ch > 0) c.ch = ' '; } } return 0; } /* tfx_inputMode * * set or query input mode * * Arguments: * L Lua State * * Lua Stack: * 1 input mode, or nil to query * * Lua Returns: * +1 current input mode */ static int tfx_inputMode(lua_State *L) { maxargs(L, 1); int mode = TB_INPUT_CURRENT; if (!lua_isnoneornil(L, 1)) mode = (int) luaL_checkinteger(L, 1); int res = tb_select_input_mode(mode); lua_pushinteger(L, res); return 1; } /* tfx_outputMode * * set or query output mode * * Arguments: * L Lua State * * Lua Stack: * 1 output mode, or nil to query * * Lua Returns: * +1 current output mode */ static int tfx_outputMode(lua_State *L) { maxargs(L, 1); int mode = TB_OUTPUT_CURRENT; if (!lua_isnoneornil(L, 1)) mode = (int) luaL_checkinteger(L, 1); int res = tb_select_output_mode(mode); lua_pushinteger(L, res); return 1; } /* tfx_pollEvent * * polls next event. * * Arguments: * L Lua State * * Lua Stack: * 1 (opt) timeout in ms, waits forever for the next event if not set * * Lua Returns: * +1 a table with the event data */ static int tfx_pollEvent(lua_State *L) { maxargs(L, 1); struct tb_event evt; int eno = 0; uint32_t started = _tfx_getMsTimer(); if (lua_gettop(L) == 1 && !lua_isnil(L, 1)) { uint32_t timeout = (int) luaL_checkinteger(L, 1); eno = initialized ? tb_peek_event(&evt, timeout) : 0; } else eno = initialized ? tb_poll_event(&evt) : 0; if (eno > 0) { lua_newtable(L); lua_pushliteral(L, "type"); switch (evt.type) { case TB_EVENT_KEY: lua_pushliteral(L, "key"); break; case TB_EVENT_RESIZE: lua_pushliteral(L, "resize"); break; #ifdef TB_EVENT_MOUSE case TB_EVENT_MOUSE: lua_pushliteral(L, "mouse"); break; #endif default: lua_pushliteral(L, "unknown"); } lua_rawset(L, -3); lua_pushliteral(L, "elapsed"); lua_pushinteger(L, _tfx_getMsTimer() - started); lua_rawset(L, -3); if (evt.type == TB_EVENT_KEY) { lua_pushliteral(L, "mod"); switch (evt.mod) { case TB_MOD_ALT: lua_pushliteral(L, "ALT"); break; default: lua_pushnil(L); } lua_rawset(L, -3); lua_pushliteral(L, "key"); if (evt.ch == 0) { lua_pushinteger(L, evt.key); } else { lua_pushnil(L); } lua_rawset(L, -3); lua_pushliteral(L, "ch"); if (evt.ch != 0) { lua_pushinteger(L, evt.ch); } else { lua_pushnil(L); } lua_rawset(L, -3); lua_pushliteral(L, "char"); char c[10]; memset(c, 0, 10); if (evt.ch < 0x7F) c[0] = evt.ch; else mini_utf8_encode(evt.ch, c, 10); lua_pushstring(L, c); lua_rawset(L, -3); } else if (evt.type == TB_EVENT_RESIZE) { lua_pushliteral(L, "w"); lua_pushinteger(L, evt.w); lua_rawset(L, -3); lua_pushliteral(L, "h"); lua_pushinteger(L, evt.h); lua_rawset(L, -3); #ifdef TB_EVENT_MOUSE } else if (evt.type == TB_EVENT_MOUSE) { lua_pushliteral(L, "x"); lua_pushinteger(L, evt.x + top_left_coord); lua_rawset(L, -3); lua_pushliteral(L, "y"); lua_pushinteger(L, evt.y + top_left_coord); lua_rawset(L, -3); lua_pushliteral(L, "key"); lua_pushinteger(L, evt.key); lua_rawset(L, -3); #endif } } else if (eno == 0) { lua_pushnil(L); } else { lua_pushnil(L); lua_pushstring(L, strerror(errno)); return 2; } return 1; } /* methods for the TfxBuffer userdata */ static const struct luaL_Reg ltfxbuf_methods [] = { {"attributes", tfx_bufAttributes}, {"clear", tfx_bufClear}, {"setcell", tfx_bufSetcell}, {"getcell", tfx_bufGetCell}, {"blit", tfx_bufBlit}, {"rect", tfx_bufRect}, {"copyregion", tfx_bufCopyRegion}, {"scrollregion", tfx_bufScrollRegion}, {"printat", tfx_printAt}, {"width", tfx_bufWidth}, {"height", tfx_bufHeight}, {"size", tfx_bufSize}, {NULL, NULL} }; /* TermFX function list */ static const struct luaL_Reg ltermfx [] ={ {"init", tfx_init}, {"shutdown", tfx_shutdown}, {"width", tfx_width}, {"height", tfx_height}, {"size", tfx_size}, {"clear", tfx_clear}, {"attributes", tfx_attributes}, {"present", tfx_present}, {"setcursor", tfx_setCursor}, {"hidecursor", tfx_hideCursor}, {"newcell", tfx_newCell}, {"setcell", tfx_setCell}, {"getcell", tfx_getCell}, {"newbuffer", tfx_newBuffer}, {"blit", tfx_blit}, {"rect", tfx_rect}, {"copyregion", tfx_copyRegion}, {"scrollregion", tfx_scrollRegion}, {"printat", tfx_printAt}, {"inputmode", tfx_inputMode}, {"outputmode", tfx_outputMode}, {"pollevent", tfx_pollEvent}, {NULL, NULL} }; /* helper: __index metamethod for the table containing the color values * for the predefined color names. As the values for these names are not * constant across the output modes, some tweaking is needed. */ static int tfx__indexColor(lua_State *L) { const char* what = luaL_checkstring(L, 2); int omode = tb_select_output_mode(TB_OUTPUT_CURRENT); int col = -1; if (!strcmp("DEFAULT", what)) col = TB_DEFAULT; else if (!strcmp("BLACK", what)) col = TB_BLACK; else if (!strcmp("RED", what)) col = TB_RED; else if (!strcmp("GREEN", what)) col = TB_GREEN; else if (!strcmp("YELLOW", what)) col = TB_YELLOW; else if (!strcmp("BLUE", what)) col = TB_BLUE; else if (!strcmp("MAGENTA", what)) col = TB_MAGENTA; else if (!strcmp("CYAN", what)) col = TB_CYAN; else if (!strcmp("WHITE", what)) col = TB_WHITE; switch (omode) { case TB_OUTPUT_256: col -= 1; break; case TB_OUTPUT_GRAYSCALE: col = (col - 1) * 3; if (col > 6) col += 1; if (col > 15) col += 1; break; case TB_OUTPUT_216: switch (col) { case TB_BLACK: col = 0; break; case TB_RED: col = 72; break; case TB_GREEN: col = 12; break; case TB_YELLOW: col = 114; break; case TB_BLUE: col = 2; break; case TB_MAGENTA: col = 74; break; case TB_CYAN: col = 14; break; case TB_WHITE: col = 129; break; default:; } break; default:; } if (col < 0) lua_pushnil(L); else lua_pushinteger(L, col); return 1; } /* add constants */ #define ADDCONST(n, v) \ lua_pushliteral(L, n); \ lua_pushinteger(L, v); \ lua_rawset(L, -3) static void tfx_addconstants(lua_State *L) { lua_pushliteral(L, "key"); lua_newtable(L); ADDCONST("F1", TB_KEY_F1); ADDCONST("F2", TB_KEY_F2); ADDCONST("F3", TB_KEY_F3); ADDCONST("F4", TB_KEY_F4); ADDCONST("F5", TB_KEY_F5); ADDCONST("F6", TB_KEY_F6); ADDCONST("F7", TB_KEY_F7); ADDCONST("F8", TB_KEY_F8); ADDCONST("F9", TB_KEY_F9); ADDCONST("F10", TB_KEY_F10); ADDCONST("F11", TB_KEY_F11); ADDCONST("F12", TB_KEY_F12); ADDCONST("INSERT", TB_KEY_INSERT); ADDCONST("DELETE", TB_KEY_DELETE); ADDCONST("HOME", TB_KEY_HOME); ADDCONST("END", TB_KEY_END); ADDCONST("PGUP", TB_KEY_PGUP); ADDCONST("PGDN", TB_KEY_PGDN); ADDCONST("ARROW_UP", TB_KEY_ARROW_UP); ADDCONST("ARROW_DOWN", TB_KEY_ARROW_DOWN); ADDCONST("ARROW_LEFT", TB_KEY_ARROW_LEFT); ADDCONST("ARROW_RIGHT", TB_KEY_ARROW_RIGHT); #ifdef TB_INPUT_MOUSE ADDCONST("MOUSE_LEFT", TB_KEY_MOUSE_LEFT); ADDCONST("MOUSE_RIGHT", TB_KEY_MOUSE_RIGHT); ADDCONST("MOUSE_MIDDLE", TB_KEY_MOUSE_MIDDLE); ADDCONST("MOUSE_RELEASE", TB_KEY_MOUSE_RELEASE); ADDCONST("MOUSE_WHEEL_UP", TB_KEY_MOUSE_WHEEL_UP); ADDCONST("MOUSE_WHEEL_DOWN", TB_KEY_MOUSE_WHEEL_DOWN); #endif ADDCONST("CTRL_TILDE", TB_KEY_CTRL_TILDE); ADDCONST("CTRL_2", TB_KEY_CTRL_2); ADDCONST("CTRL_A", TB_KEY_CTRL_A); ADDCONST("CTRL_B", TB_KEY_CTRL_B); ADDCONST("CTRL_C", TB_KEY_CTRL_C); ADDCONST("CTRL_D", TB_KEY_CTRL_D); ADDCONST("CTRL_E", TB_KEY_CTRL_E); ADDCONST("CTRL_F", TB_KEY_CTRL_F); ADDCONST("CTRL_G", TB_KEY_CTRL_G); ADDCONST("BACKSPACE", TB_KEY_BACKSPACE); ADDCONST("CTRL_H", TB_KEY_CTRL_H); ADDCONST("TAB", TB_KEY_TAB); ADDCONST("CTRL_I", TB_KEY_CTRL_I); ADDCONST("CTRL_J", TB_KEY_CTRL_J); ADDCONST("CTRL_K", TB_KEY_CTRL_K); ADDCONST("CTRL_L", TB_KEY_CTRL_L); ADDCONST("ENTER", TB_KEY_ENTER); ADDCONST("CTRL_M", TB_KEY_CTRL_M); ADDCONST("CTRL_N", TB_KEY_CTRL_N); ADDCONST("CTRL_O", TB_KEY_CTRL_O); ADDCONST("CTRL_P", TB_KEY_CTRL_P); ADDCONST("CTRL_Q", TB_KEY_CTRL_Q); ADDCONST("CTRL_R", TB_KEY_CTRL_R); ADDCONST("CTRL_S", TB_KEY_CTRL_S); ADDCONST("CTRL_T", TB_KEY_CTRL_T); ADDCONST("CTRL_U", TB_KEY_CTRL_U); ADDCONST("CTRL_V", TB_KEY_CTRL_V); ADDCONST("CTRL_W", TB_KEY_CTRL_W); ADDCONST("CTRL_X", TB_KEY_CTRL_X); ADDCONST("CTRL_Y", TB_KEY_CTRL_Y); ADDCONST("CTRL_Z", TB_KEY_CTRL_Z); ADDCONST("ESC", TB_KEY_ESC); ADDCONST("CTRL_LSQ_BRACKET", TB_KEY_CTRL_LSQ_BRACKET); ADDCONST("CTRL_3", TB_KEY_CTRL_3); ADDCONST("CTRL_4", TB_KEY_CTRL_4); ADDCONST("CTRL_BACKSLASH", TB_KEY_CTRL_BACKSLASH); ADDCONST("CTRL_5", TB_KEY_CTRL_5); ADDCONST("CTRL_RSQ_BRACKET", TB_KEY_CTRL_RSQ_BRACKET); ADDCONST("CTRL_6", TB_KEY_CTRL_6); ADDCONST("CTRL_7", TB_KEY_CTRL_7); ADDCONST("CTRL_SLASH", TB_KEY_CTRL_SLASH); ADDCONST("CTRL_UNDERSCORE", TB_KEY_CTRL_UNDERSCORE); ADDCONST("SPACE", TB_KEY_SPACE); ADDCONST("BACKSPACE2", TB_KEY_BACKSPACE2); ADDCONST("CTRL_8", TB_KEY_CTRL_8); lua_rawset(L, -3); lua_pushliteral(L, "color"); lua_newtable(L); lua_newtable(L); lua_pushliteral(L, "__index"); lua_pushcfunction(L, tfx__indexColor); lua_rawset(L, -3); lua_setmetatable(L, -2); lua_rawset(L, -3); lua_pushliteral(L, "format"); lua_newtable(L); ADDCONST("BOLD", TB_BOLD); ADDCONST("UNDERLINE", TB_UNDERLINE); ADDCONST("REVERSE", TB_REVERSE); lua_rawset(L, -3); lua_pushliteral(L, "input"); lua_newtable(L); ADDCONST("ESC", TB_INPUT_ESC); ADDCONST("ALT", TB_INPUT_ALT); #ifdef TB_INPUT_MOUSE ADDCONST("MOUSE", TB_INPUT_MOUSE); #endif lua_rawset(L, -3); lua_pushliteral(L, "output"); lua_newtable(L); ADDCONST("NORMAL", TB_OUTPUT_NORMAL); ADDCONST("COL256", TB_OUTPUT_256); ADDCONST("COL216", TB_OUTPUT_216); ADDCONST("GRAYSCALE", TB_OUTPUT_GRAYSCALE); ADDCONST("GREYSCALE", TB_OUTPUT_GRAYSCALE); lua_rawset(L, -3); } /* luaopen_ltermbox * * open and initialize this library */ int luaopen_termfx(lua_State *L) { struct timeval tv; gettimeofday(&tv, NULL); mstimer_tm0 = tv.tv_sec * 1000 + tv.tv_usec / 1000; luaL_newlib(L, ltermfx); tfx_color_init(L); tfx_addconstants(L); lua_pushliteral(L, "_VERSION"); lua_pushliteral(L, _VERSION); lua_rawset(L, -3); luaL_newmetatable(L, TFXCELL); luaL_setfuncs(L, tfx_CellMeta, 0); lua_pop(L, 1); luaL_newmetatable(L, TFXBUFFER); luaL_setfuncs(L, tfx_BufferMeta, 0); lua_pushliteral(L, "__index"); luaL_newlib(L, ltfxbuf_methods); lua_rawset(L, -3); lua_pop(L, 1); /* shutdown termbox or suffer the consequences */ atexit(_tfx_doShutdown); return 1; }