├── .gitignore ├── .luacov ├── .travis.yml ├── .travis ├── c_coverage.sh ├── install_rocks.sh ├── install_rote.sh ├── nsswitch_c_678.supp ├── platform.sh ├── setenv_lua.sh ├── setup_lua.sh └── test_with_valgrind.sh ├── LICENSE ├── README.md ├── demo └── boxshell.lua ├── lua-rote-dev-1.rockspec ├── spec ├── RoteTerm_spec.lua ├── attr_spec.lua ├── boxshell_spec.lua ├── color_spec.lua ├── cursesConsts_spec.lua └── load_module_spec.lua └── src ├── attr.c ├── color.c ├── cursesConsts.lua ├── lua-rote.c ├── lua-rote.h ├── ncurses.c └── rote.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # text editor files 32 | *.swp 33 | *.autosave 34 | 35 | # astyle, hg 36 | *.orig 37 | *.org 38 | 39 | # logs 40 | *.log 41 | 42 | # qmake 43 | *.pro.user 44 | *-build-* 45 | 46 | # coverage measurement tools 47 | c.report.json 48 | *.gcov 49 | *.gcda 50 | *.gcno 51 | 52 | # MXE 53 | usr 54 | include 55 | mxe 56 | 57 | # cmake 58 | CMakeFiles 59 | CMakeCache.txt 60 | CTestTestfile.cmake 61 | cmake_install.cmake 62 | install_manifest.txt 63 | CTestCostData.txt 64 | 65 | # nsis 66 | nsis* 67 | 68 | # my patched busted 69 | exitless-busted 70 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | --- Global configuration file. Copy, customize and store in your 2 | -- project folder as '.luacov' for project specific configuration 3 | -- @class module 4 | -- @name luacov.defaults 5 | return { 6 | 7 | -- default filename to load for config options if not provided 8 | -- only has effect in 'luacov.defaults.lua' 9 | ["configfile"] = ".luacov", 10 | 11 | -- filename to store stats collected 12 | ["statsfile"] = "luacov.stats.out", 13 | 14 | -- filename to store report 15 | ["reportfile"] = "luacov.report.out", 16 | 17 | -- luacov.stats file updating frequency. 18 | -- The lower this value - the more frequenty results will be written out to luacov.stats 19 | -- You may want to reduce this value for short lived scripts (to for example 2) to avoid losing coverage data. 20 | ["savestepsize"] = 100, 21 | 22 | -- Run reporter on completion? (won't work for ticks) 23 | runreport = true, 24 | 25 | -- Delete stats file after reporting? 26 | deletestats = false, 27 | 28 | -- Process Lua code loaded from raw strings 29 | -- (that is, when the 'source' field in the debug info 30 | -- does not start with '@') 31 | codefromstrings = false, 32 | 33 | -- Patterns for files to include when reporting 34 | -- all will be included if nothing is listed 35 | -- (exclude overrules include, do not include 36 | -- the .lua extension, path separator is always '/') 37 | ["include"] = { 38 | "src/.*$", 39 | "demo/.*$", 40 | }, 41 | 42 | -- Patterns for files to exclude when reporting 43 | -- all will be included if nothing is listed 44 | -- (exclude overrules include, do not include 45 | -- the .lua extension, path separator is always '/') 46 | ["exclude"] = { 47 | "luacov$", 48 | "luacov/reporter$", 49 | "luacov/defaults$", 50 | "luacov/runner$", 51 | "luacov/stats$", 52 | "luacov/tick$", 53 | }, 54 | 55 | } 56 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | env: 4 | global: 5 | - LUAROCKS=2.2.0 6 | matrix: 7 | - LUA=lua5.1 8 | - LUA=lua5.2 9 | - LUA=lua5.3 10 | - LUA=luajit 11 | 12 | before_install: 13 | - sudo apt-get update 14 | - source .travis/setenv_lua.sh 15 | - bash .travis/install_rocks.sh 16 | - bash .travis/install_rote.sh 17 | - sudo apt-get install valgrind 18 | - sudo pip install cpp-coveralls 19 | 20 | script: 21 | - bash .travis/test_with_valgrind.sh 22 | - bash .travis/c_coverage.sh 23 | 24 | after_success: 25 | - coveralls --exclude install -b . --dump c.report.json 26 | - luacov-coveralls -j c.report.json -v 27 | -------------------------------------------------------------------------------- /.travis/c_coverage.sh: -------------------------------------------------------------------------------- 1 | luarocks make --local \ 2 | CFLAGS="-O0 -fPIC -ftest-coverage -fprofile-arcs" \ 3 | LIBFLAG="-shared --coverage" && busted 4 | -------------------------------------------------------------------------------- /.travis/install_rocks.sh: -------------------------------------------------------------------------------- 1 | luarocks install luasec OPENSSL_LIBDIR=/usr/lib/x86_64-linux-gnu --local 2 | luarocks install busted --local 3 | luarocks install luaposix --local 4 | luarocks install lcurses --local 5 | luarocks install luacov --local 6 | luarocks install luacov-coveralls --local 7 | -------------------------------------------------------------------------------- /.travis/install_rote.sh: -------------------------------------------------------------------------------- 1 | sudo apt-get install libncurses5-dev 2 | 3 | wget http://sourceforge.net/projects/rote/files/rote/rote-0.2.8/rote-0.2.8.tar.gz 4 | tar -xf rote-0.2.8.tar.gz 5 | cd rote-0.2.8/ 6 | ./configure --prefix=/usr 7 | make 8 | sudo make install 9 | 10 | cd .. 11 | sudo rm -rf rote-0.2.8/ 12 | -------------------------------------------------------------------------------- /.travis/nsswitch_c_678.supp: -------------------------------------------------------------------------------- 1 | { 2 | nsswitch_c_678 3 | Memcheck:Leak 4 | fun:malloc 5 | fun:nss_parse_service_list 6 | fun:__nss_database_lookup 7 | } 8 | -------------------------------------------------------------------------------- /.travis/platform.sh: -------------------------------------------------------------------------------- 1 | if [ -z "${PLATFORM:-}" ]; then 2 | PLATFORM=$TRAVIS_OS_NAME; 3 | fi 4 | 5 | if [ "$PLATFORM" == "osx" ]; then 6 | PLATFORM="macosx"; 7 | fi 8 | 9 | if [ -z "$PLATFORM" ]; then 10 | if [ "$(uname)" == "Linux" ]; then 11 | PLATFORM="linux"; 12 | else 13 | PLATFORM="macosx"; 14 | fi; 15 | fi 16 | -------------------------------------------------------------------------------- /.travis/setenv_lua.sh: -------------------------------------------------------------------------------- 1 | export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin 2 | export PATH=${PATH}:${TRAVIS_BUILD_DIR}/install/luarocks/bin 3 | export PATH=${PATH}:~/.luarocks/bin 4 | bash .travis/setup_lua.sh 5 | eval `$HOME/.lua/luarocks path` 6 | -------------------------------------------------------------------------------- /.travis/setup_lua.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # A script for setting up environment for travis-ci testing. 4 | # Sets up Lua and Luarocks. 5 | # LUA must be "lua5.1", "lua5.2" or "luajit". 6 | # luajit2.0 - master v2.0 7 | # luajit2.1 - master v2.1 8 | 9 | set -eufo pipefail 10 | 11 | LUAJIT_BASE="LuaJIT-2.0.4" 12 | 13 | source .travis/platform.sh 14 | 15 | LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua 16 | 17 | LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks 18 | 19 | mkdir $HOME/.lua 20 | 21 | LUAJIT="no" 22 | 23 | if [ "$PLATFORM" == "macosx" ]; then 24 | if [ "$LUA" == "luajit" ]; then 25 | LUAJIT="yes"; 26 | fi 27 | if [ "$LUA" == "luajit2.0" ]; then 28 | LUAJIT="yes"; 29 | fi 30 | if [ "$LUA" == "luajit2.1" ]; then 31 | LUAJIT="yes"; 32 | fi; 33 | elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then 34 | LUAJIT="yes"; 35 | fi 36 | 37 | mkdir -p "$LUA_HOME_DIR" 38 | 39 | if [ "$LUAJIT" == "yes" ]; then 40 | 41 | if [ "$LUA" == "luajit" ]; then 42 | curl http://luajit.org/download/$LUAJIT_BASE.tar.gz | tar xz; 43 | else 44 | git clone http://luajit.org/git/luajit-2.0.git $LUAJIT_BASE; 45 | fi 46 | 47 | cd $LUAJIT_BASE 48 | 49 | if [ "$LUA" == "luajit2.1" ]; then 50 | git checkout v2.1; 51 | fi 52 | 53 | make && make install PREFIX="$LUA_HOME_DIR" 54 | 55 | if [ "$LUA" == "luajit2.1" ]; then 56 | ln -s $LUA_HOME_DIR/bin/luajit-2.1.0-beta1 $HOME/.lua/luajit 57 | ln -s $LUA_HOME_DIR/bin/luajit-2.1.0-beta1 $HOME/.lua/lua; 58 | else 59 | ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit 60 | ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua; 61 | fi; 62 | 63 | else 64 | 65 | if [ "$LUA" == "lua5.1" ]; then 66 | curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz 67 | cd lua-5.1.5; 68 | elif [ "$LUA" == "lua5.2" ]; then 69 | curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz 70 | cd lua-5.2.4; 71 | elif [ "$LUA" == "lua5.3" ]; then 72 | curl http://www.lua.org/ftp/lua-5.3.0.tar.gz | tar xz 73 | cd lua-5.3.0; 74 | fi 75 | 76 | make $PLATFORM 77 | make INSTALL_TOP="$LUA_HOME_DIR" install; 78 | 79 | ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua 80 | ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac; 81 | 82 | fi 83 | 84 | cd $TRAVIS_BUILD_DIR 85 | 86 | lua -v 87 | 88 | LUAROCKS_BASE=luarocks-$LUAROCKS 89 | 90 | curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz 91 | 92 | cd $LUAROCKS_BASE 93 | 94 | if [ "$LUA" == "luajit" ]; then 95 | ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; 96 | elif [ "$LUA" == "luajit2.0" ]; then 97 | ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; 98 | elif [ "$LUA" == "luajit2.1" ]; then 99 | ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR"; 100 | else 101 | ./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR" 102 | fi 103 | 104 | make build && make install 105 | 106 | ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks 107 | 108 | cd $TRAVIS_BUILD_DIR 109 | 110 | luarocks --version 111 | 112 | rm -rf $LUAROCKS_BASE 113 | 114 | if [ "$LUAJIT" == "yes" ]; then 115 | rm -rf $LUAJIT_BASE; 116 | elif [ "$LUA" == "lua5.1" ]; then 117 | rm -rf lua-5.1.5; 118 | elif [ "$LUA" == "lua5.2" ]; then 119 | rm -rf lua-5.2.4; 120 | elif [ "$LUA" == "lua5.3" ]; then 121 | rm -rf lua-5.3.0; 122 | fi 123 | -------------------------------------------------------------------------------- /.travis/test_with_valgrind.sh: -------------------------------------------------------------------------------- 1 | if [ "$LUA" != "luajit" ]; then 2 | # re-build in Debug mode 3 | luarocks make --local CFLAGS="-O0 -g -fPIC" 4 | # make busted which does not call os.exit 5 | echo 'os.exit = function() end' > exitless-busted 6 | echo 'require "busted.runner"({ standalone = false, batch = true })' \ 7 | >> exitless-busted 8 | # valgrind... 9 | valgrind --error-exitcode=1 --leak-check=full \ 10 | --gen-suppressions=all \ 11 | --suppressions=.travis/nsswitch_c_678.supp \ 12 | lua exitless-busted 13 | fi 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | (This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.) 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | {description} 474 | Copyright (C) {year} {fullname} 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | {signature of Ty Coon}, 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | 506 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | 3 | [![Build Status](https://travis-ci.org/starius/lua-rote.png?branch=master)](https://travis-ci.org/starius/lua-rote) 4 | [![Coverage Status](https://coveralls.io/repos/starius/lua-rote/badge.png?branch=master)](https://coveralls.io/r/starius/lua-rote?branch=master) 5 | [![License](http://img.shields.io/badge/License-LGPL2.1-brightgreen.png)][4] 6 | 7 | ## Description 8 | 9 | [ROTE][1] is a simple C library for VT102 terminal emulation. 10 | It allows the programmer to set up virtual 'screens' and send 11 | them data. The virtual screens will emulate the behavior of a 12 | VT102 terminal, interpreting escape sequences, control 13 | characters and such. The library supports ncurses as well so 14 | that you may render the virtual screen to the real screen 15 | when you need to. 16 | 17 | There are several programs that do terminal emulation, such 18 | as xterm, rxvt, screen and even the Linux console driver 19 | itself. However, it is not easy to isolate their terminal 20 | emulation logic and put it in a module that can be easily 21 | reused in other programs. That's where the ROTE library 22 | comes in. 23 | 24 | The goal of the lua-rote library is to provide terminal 25 | emulation support for Lua applications, making it 26 | possible to write programs that display terminals in 27 | embedded windows within them, or even monitor the display 28 | produced by other programs. The lua-rote library depends 29 | only on Lua, ROTE itself, ncurses, lcurses and luaposix. 30 | 31 | The ROTE library is able to render the 32 | virtual screens to the physical screen (actually any 33 | ncurses window) and can also translate ncurses key codes to 34 | the escape sequences the Linux console would have produced 35 | (and feed them into the terminal). Using ncurses is not 36 | mandatory however, and ROTE will work fine without it, but 37 | in that case the application must take care of drawing the 38 | terminal to the screen in whichever way it sees fit. 39 | 40 | ROTE also encapsulates the functionality needed to execute 41 | a child process using the virtual screen as the controlling 42 | terminal. It will handle the creation of the 43 | pseudo-terminal and the child process. All the application 44 | has to do is tell it the command to run in the terminal and 45 | call an update function at regular intervals to allow the 46 | terminal to update itself. 47 | 48 | ROTE is extremely useful to programmatically interact 49 | with curses applications (e.g., for unit testing). 50 | 51 | ## Prerequisites 52 | 53 | - Lua 5.1, 5.2, 5.3 or LuaJIT 54 | - luaposix 55 | - curses (binary + headers) 56 | - lcurses (install after installing curses headers!) 57 | - [ROTE][1] (install after installing curses headers!) 58 | 59 | > Curses and luaposix are needed for drawing state of ROTE 60 | > terminal on curses' WINDOW object 61 | > (method [RoteTerm:draw()](#draw)). 62 | > If you do not need this feature and want to exclude these 63 | > dependencies, then remove CURSES, lcurses and luaposix from 64 | > file `lua-rote-*.rockspec`. 65 | 66 | See [shell script][5] with installation 67 | commands for Debian Wheezy. 68 | 69 | ## Installation 70 | 71 | This library is built using [LuaRocks](http://luarocks.org). 72 | 73 | ### Option 1: install from LuaRocks server 74 | 75 | ```bash 76 | $ luarocks install lua-rote 77 | ``` 78 | 79 | If you have installed ROTE to prefix other than "/usr", 80 | you have to provide this path to LuaRocks. 81 | For example, if you have installed ROTE to "/usr/local", 82 | use the following command: 83 | 84 | ```bash 85 | $ luarocks install lua-rote ROTE_DIR=/usr/local 86 | ``` 87 | 88 | ### Option 2: install from local source tree 89 | 90 | ```bash 91 | $ git clone https://github.com/starius/lua-rote.git 92 | $ cd lua-rote 93 | $ luarocks make 94 | ``` 95 | 96 | ## Running unit tests 97 | 98 | Unit tests are written using unit testing framework 99 | [busted](http://olivinelabs.com/busted/). 100 | Unit tests can serve as reference documentation and 101 | code examples. 102 | 103 | To run unit tests, install busted from LuaRocks: 104 | 105 | ```bash 106 | $ luarocks install busted 107 | ``` 108 | 109 | Go to the source folder of lua-rote and run command `busted`: 110 | 111 | ```bash 112 | $ busted 113 | ++++++++++++++++++++++++++++++++ 114 | 32 successes / 0 failures / 0 errors / 0 pending : 1.5 seconds 115 | ``` 116 | 117 | ## Running the demo 118 | 119 | Program [boxshell.lua][6] is a clone of 120 | ROTE's example program "boxshell.c" (file "demo/boxshell.c" 121 | in ROTE's source tree). 122 | Both programs include the following steps: 123 | 124 | - start curses, 125 | - fill the screen with blue, 126 | - create curses window in the middle of the screen, 127 | - start ROTE terminal, fork bash inside, 128 | - do in a loop until child process dies: 129 | - redraw curses window accorsing to ROTE terminal, 130 | - `getch()`, results of which are passed to ROTE terminal. 131 | 132 | Run `lua demo/boxshell.lua`, `ls`, `busted`: 133 | 134 | ![boxshell.lua](http://i.imgur.com/F5K9gJt.png) 135 | 136 | > Currently lua-rote does not support unicode characters, 137 | > that is why busted was changed to produce "+" instead of "●". 138 | 139 | > There are some differences between boxshell.c and 140 | > boxshell.lua. Program boxshell.lua can fork other commands 141 | > as well as bash. boxshell.c uses `nodelay` mode 142 | > repeating draw-getch cycle without a delay, 143 | > while boxshell.lua uses `halfdelay` mode repeating 144 | > draw-getch cycle 10 times a second. 145 | > That is why boxshell.c constantly consumes 100% CPU, 146 | > while boxshell.lua consumes almost no CPU when inactive. 147 | 148 | ## Reference 149 | 150 | ### Module rote 151 | 152 | Library lua-rote is loaded from module "rote": 153 | 154 | ```lua 155 | rote = require 'rote' 156 | ``` 157 | 158 | All code of the library "lives" inside this module. 159 | 160 | ### Class RoteTerm 161 | 162 | The main part of the library is class RoteTerm. 163 | It wraps C structure RoteTerm, declared in library ROTE. 164 | RoteTerm represents terminal emulator. 165 | 166 | Create a new virtual terminal with the given dimensions. 167 | (Height is 24 rows, width is 80 columns.) 168 | 169 | ```lua 170 | rt = rote.RoteTerm(24, 80) 171 | ``` 172 | 173 | Instance of RoteTerm is destroyed automatically 174 | when the corresponding Lua object is collected. 175 | 176 | ### Start child process 177 | 178 | Start a forked process in the terminal: 179 | 180 | ```lua 181 | pid = rt:forkPty('less /some/file') 182 | ``` 183 | 184 | The command will be interpreted by '/bin/sh -c'. 185 | 186 | Returns PID of the child process. 187 | On error returns `-1`. 188 | Notice that passing an invalid command will not cause 189 | an error at this level: the shell will try to execute 190 | the command and will exit with status 127. You can catch 191 | that by installing a `SIGCHLD` handler if you want. 192 | 193 | > If you want to be notified when child processes exits, 194 | > you should handle the `SIGCHLD` signal. 195 | > If, on the other hand, you want to ignore exitting 196 | > child processes, you should set the `SIGCHLD` handler to 197 | > `SIG_IGN` to prevent child processes from hanging 198 | > around the system as 'zombie processes'. 199 | > 200 | > You can use luaposix to manage child processes as described 201 | > above. See file [demo/boxshell.lua][6]. 202 | > 203 | > Continuing to write to a RoteTerm whose child process 204 | > has died does not accomplish a lot, but is not an error 205 | > and should not cause your program to crash or block 206 | > indefinitely or anything of that sort :-) 207 | > 208 | > If, however, you want to be tidy and inform the RoteTerm 209 | > that its child has died, call method `forsakeChild` 210 | > when appropriate. 211 | 212 | You can get the PID later by calling `rt:childPid()`. 213 | 214 | Disconnect the RoteTerm from its forked child process: 215 | 216 | ```lua 217 | rt:forsakeChild() 218 | ``` 219 | 220 | ### Getting contents of the terminal 221 | 222 | You can get number of rows and columns of the terminal: 223 | 224 | ```lua 225 | print(rt:rows()) -- integer 226 | print(rt:cols()) -- integer 227 | ``` 228 | 229 | Get cursor coordinates: 230 | 231 | ```lua 232 | print(rt:row()) -- integer 233 | print(rt:col()) -- integer 234 | ``` 235 | 236 | Before getting any output from the child process, call method 237 | `rt:update()` to update internal state of RoteTerm. 238 | 239 | You can get value of character and attribute of any cell: 240 | 241 | ```lua 242 | row = 0 243 | col = 0 244 | print(rt:cellChar(row, col)) -- string of length 1 245 | attr = rt:cellAttr(row, col) -- integer 246 | ``` 247 | 248 | lua-rote provides [several functions](#handling-attributes) 249 | to handle attribute values. 250 | 251 | Get current attribute, that is the attribute that will be 252 | used for newly characters: 253 | 254 | ```lua 255 | print(rt:attr()) -- integer 256 | ``` 257 | 258 | Get a row as a string (not terminated with `\n`): 259 | 260 | ```lua 261 | row = 0 262 | print(rt:rowText(row)) -- string 263 | ``` 264 | 265 | Get whole terminal as a string (rows are terminated with `\n`): 266 | 267 | ```lua 268 | print(rt:termText()) -- string 269 | ``` 270 | 271 | 272 | Draw contents of ROTE terminal on curses WINDOW: 273 | 274 | ```lua 275 | curses = require 'curses' 276 | -- setup curses, see demo/boxshell.lua 277 | window = ... 278 | rt = ... 279 | start_row = 0 280 | start_col = 0 281 | rt:draw(window, start_row, start_col) 282 | ``` 283 | 284 | ### Changing the terminal state 285 | 286 | You can directly change internal state of RoteTerm by 287 | calling the following methods: 288 | 289 | ```lua 290 | rt:setCellChar(row, col, character) -- character at (row, col) 291 | rt:setCellAttr(row, col, attr) -- attribute at (row, col) 292 | rt:setAttr(attr) -- current attribute 293 | ``` 294 | 295 | You can pass data to the child process or to the terminal: 296 | 297 | ```lua 298 | -- Puts data ':wq\n' into the terminal. 299 | -- If there is a forked process, the data will be sent to it. 300 | -- If there is no forked process, the data will simply 301 | -- be injected into the terminal (as in inject()). 302 | rt:write(':wq\n') 303 | 304 | -- Inject data directly into the terminal. 305 | rt:inject(':wq\n') 306 | 307 | -- Indicates to the terminal that the key has been pressed. 308 | -- Appropriate escape sequence is passed to method write(). 309 | local keycode = string.byte('\n') -- integer 310 | rt:keyPress(keycode) 311 | ``` 312 | 313 | You can get values of keycodes from [curses][3]. 314 | Unfortunately it should be initialized, otherwise 315 | constants are not available. Initialization of curses 316 | may be undesirable in an application (testing tool), 317 | which runs another application, which runs curses. 318 | There is a workaround: module [`"rote.cursesConsts"`][14]. 319 | It uses rote to run child Lua process, which initializes 320 | curses and prints values of constants. 321 | The module `"rote.cursesConsts"` returns them 322 | as a table. 323 | 324 | ### Snapshots 325 | 326 | ```lua 327 | -- take a snapshot of the current contents of the terminal 328 | snapshot = rt:takeSnapshot() 329 | -- ... do something ... 330 | -- restore a snapshot previously taken 331 | rt:restoreSnapshot(snapshot) 332 | ``` 333 | 334 | Snapshot object is deleted automatically when the 335 | corresponding Lua object is collected. 336 | 337 | 338 | 339 | ### Handling attributes 340 | 341 | An 'attribute' as used in this library means an 8-bit value 342 | that conveys a foreground color code, a background color code, 343 | and the bold and blink bits. Each cell in the virtual terminal 344 | screen is associated with an attribute that specifies 345 | its appearance. 346 | 347 | The bits of an attribute, from most significant to 348 | least significant, are 349 | 350 | ``` 351 | bit: 7 6 5 4 3 2 1 0 352 | content: S F F F H B B B 353 | | `-,-' | `-,-' 354 | | | | | 355 | | | | `----- 3-bit background color (0 - 7) 356 | | | `--------- blink bit 357 | | `------------- 3-bit foreground color (0 - 7) 358 | `----------------- bold bit 359 | ``` 360 | 361 | 362 | Color codes: 363 | 364 | - 0 = black, 365 | - 1 = red, 366 | - 2 = green, 367 | - 3 = yellow, 368 | - 4 = blue, 369 | - 5 = magenta, 370 | - 6 = cyan, 371 | - 7 = white. 372 | 373 | There are functions provided to "pack" and "unpack" 374 | attribute bits: 375 | 376 | ```lua 377 | foreground, background, bold, blink = rote.fromAttr(attr) 378 | attr = rote.toAttr(foreground, background, bold, blink) 379 | -- foreground and background are integers (0 - 7) 380 | -- bold and blink are booleans 381 | ``` 382 | 383 | The library provides tables converting color codes to and from 384 | human readable names: 385 | 386 | ```lua 387 | print(rote.color2name[2]) -- prints "green" 388 | print(rote.name2color.green) -- prints "2" 389 | ``` 390 | 391 | ## Bugs 392 | 393 | - Unicode characters are printed and read with errors. 394 | - Method `RoteTerm:draw()` is [unreliable][2]. 395 | - ROTE can't read cell 0x0 in 1x2 window when 396 | reads second time. It seems to be related to 397 | low number of columns. 398 | 399 | [Report a bug][7] 400 | 401 | ## Author 402 | 403 | Corresponding author: Boris Nagaev, email: bnagaev@gmail.com 404 | 405 | Copyright (C) 2015 Boris Nagaev 406 | 407 | See the [LICENSE][4] file for terms of use. 408 | 409 | ROTE was written by Bruno T. C. de Oliveira, 410 | see [rote.sourceforge.net][1] for more information. 411 | 412 | ## Links 413 | 414 | - [Home page][13] 415 | - [ROTE][1] 416 | - [Report a bug][7] 417 | - [Reddit][8] 418 | - [Хабрахабр][9] 419 | - [lua-l][12] 420 | - [Busted][10] 421 | - [lua-travis-example][11] 422 | 423 | [1]: http://rote.sourceforge.net/ 424 | [2]: https://travis-ci.org/starius/lua-rote/jobs/54479120#L1160 425 | [3]: https://lcurses.github.io/lcurses/ 426 | [4]: LICENSE 427 | [5]: .travis/install_rote.sh 428 | [6]: demo/boxshell.lua 429 | [7]: https://github.com/starius/lua-rote/issues/new 430 | [8]: https://www.reddit.com/r/lua/comments/30ast4/ann_luarote_lua_binding_to_rote_terminal/ 431 | [9]: http://habrahabr.ru/post/254089/ 432 | [10]: http://olivinelabs.com/busted/ 433 | [11]: https://github.com/moteus/lua-travis-example 434 | [12]: http://lua-users.org/lists/lua-l/2015-03/msg00325.html 435 | [13]: http://starius.github.io/lua-rote 436 | [14]: src/cursesConsts.lua 437 | -------------------------------------------------------------------------------- /demo/boxshell.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | -- This file is part of lua-rote, Lua binding to ROTE 4 | -- Terminal Emulation library 5 | -- Copyright (C) 2015 Boris Nagaev 6 | -- See the LICENSE file for terms of use. 7 | 8 | -- ROTE is a simple C library for VT102 terminal emulation. 9 | -- See http://rote.sourceforge.net/ 10 | 11 | -- Just a simple example program that creates a terminal 12 | -- in a frame and lets the user interact with it. 13 | 14 | -- based on demo/boxshell.c from ROTE 15 | 16 | local curses = require 'curses' 17 | local signal = require 'posix.signal' 18 | local rote = require 'rote' 19 | local name2color = rote.name2color 20 | 21 | local getout = false 22 | 23 | signal.signal(signal.SIGCHLD, function(--[[signo]]) 24 | getout = true 25 | end) 26 | 27 | local stdscr = curses.initscr() 28 | curses.echo(false) 29 | curses.start_color() 30 | curses.raw(true) 31 | local tenths_of_second = 1 32 | curses.halfdelay(tenths_of_second) -- halfdelay mode 33 | stdscr:keypad(true) -- necessary to use rt:keyPress() 34 | local screen_h, screen_w = stdscr:getmaxyx() 35 | 36 | local function makePair(foreground, background) 37 | return background * 8 + 7 - foreground 38 | end 39 | 40 | -- initialize the color pairs the way rt:draw() expects it 41 | for foreground = 0, 7 do 42 | for background = 0, 7 do 43 | if foreground ~= 7 or background ~= 0 then 44 | local pair = makePair(foreground, background) 45 | curses.init_pair(pair, foreground, background) 46 | end 47 | end 48 | end 49 | 50 | -- paint the screen blue 51 | local background = name2color.blue 52 | local foreground = name2color.white 53 | local pair = makePair(foreground, background) 54 | stdscr:attrset(curses.color_pair(pair)) 55 | for _ = 0, screen_h - 1 do 56 | for _ = 0, screen_w - 1 do 57 | stdscr:addch(string.byte(' ')) 58 | end 59 | end 60 | stdscr:refresh() 61 | 62 | -- create a window with a frame 63 | local term_win = curses.newwin(22, 72, 1, 4) 64 | -- black over white 65 | local pair2 = makePair(name2color.black, name2color.white) 66 | term_win:attrset(curses.color_pair(pair2)) 67 | term_win:border(0, 0, 0, 0, 0, 0, 0, 0) 68 | term_win:mvaddstr(0, 27, " Term In a Box ") 69 | term_win:refresh() 70 | 71 | local rt = rote.RoteTerm(20, 70) 72 | local command = arg[1] or "/bin/bash --login" 73 | rt:forkPty(command) 74 | 75 | while not getout do 76 | rt:draw(term_win, 1, 1) 77 | term_win:refresh() 78 | local ch = stdscr:getch() 79 | local ERR = 255 80 | if type(ch) == 'number' and ch ~= ERR then 81 | rt:keyPress(ch) 82 | end 83 | end 84 | 85 | curses.endwin() 86 | -------------------------------------------------------------------------------- /lua-rote-dev-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-rote" 2 | version = "dev-1" 3 | source = { 4 | url = "git://github.com/starius/lua-rote.git" 5 | } 6 | description = { 7 | summary = "Lua binding to ROTE, Terminal Emulation library", 8 | homepage = "https://starius.github.io/lua-rote", 9 | license = "LGPL 2.1", 10 | detailed = [[ 11 | lua-rote is a Lua binding to ROTE, Terminal Emulation library 12 | 13 | ROTE is a simple C library for VT102 terminal emulation. 14 | It allows the programmer to set up virtual 'screens' and send 15 | them data. The virtual screens will emulate the behavior of a 16 | VT102 terminal, interpreting escape sequences, control 17 | characters and such. The library supports ncurses as well so 18 | that you may render the virtual screen to the real screen 19 | when you need to. 20 | 21 | There are several programs that do terminal emulation, such 22 | as xterm, rxvt, screen and even the Linux console driver 23 | itself. However, it is not easy to isolate their terminal 24 | emulation logic and put it in a module that can be easily 25 | reused in other programs. That's where the ROTE library 26 | comes in. 27 | 28 | The goal of the lua-rote library is to provide terminal 29 | emulation support for Lua applications, making it 30 | possible to write programs that display terminals in 31 | embedded windows within them, or even monitor the display 32 | produced by other programs. The lua-rote library depends 33 | only on Lua, ROTE itself, ncurses, lcurses and luaposix. 34 | 35 | The ROTE library is able to render the 36 | virtual screens to the physical screen (actually any 37 | ncurses window) and can also translate ncurses key codes to 38 | the escape sequences the Linux console would have produced 39 | (and feed them into the terminal). Using ncurses is not 40 | mandatory however, and ROTE will work fine without it, but 41 | in that case the application must take care of drawing the 42 | terminal to the screen in whichever way it sees fit. 43 | 44 | ROTE also encapsulates the functionality needed to execute 45 | a child process using the virtual screen as the controlling 46 | terminal. It will handle the creation of the 47 | pseudo-terminal and the child process. All the application 48 | has to do is tell it the command to run in the terminal and 49 | call an update function at regular intervals to allow the 50 | terminal to update itself. 51 | 52 | ROTE is extremely useful to programmatically interact 53 | with curses applications (e.g., for unit testing). 54 | ]], 55 | } 56 | dependencies = { 57 | "lua >= 5.1", 58 | "luaposix", 59 | "lcurses", 60 | } 61 | external_dependencies = { 62 | ROTE = { 63 | header = "rote/rote.h" 64 | }, 65 | CURSES = { 66 | header = "curses.h" 67 | }, 68 | } 69 | build = { 70 | type = "builtin", 71 | modules = { 72 | ['rote'] = { 73 | sources = { 74 | "src/rote.c", 75 | "src/attr.c", 76 | "src/color.c", 77 | "src/ncurses.c", 78 | "src/lua-rote.c", 79 | }, 80 | incdirs = {"$(ROTE_INCDIR)", "$(CURSES_INCDIR)"}, 81 | libdirs = {"$(ROTE_LIBDIR)"}, 82 | libraries = {"rote"}, 83 | }, 84 | ['rote.cursesConsts'] = 'src/cursesConsts.lua', 85 | }, 86 | install = { 87 | bin = { "demo/boxshell.lua" } 88 | }, 89 | } 90 | -------------------------------------------------------------------------------- /spec/RoteTerm_spec.lua: -------------------------------------------------------------------------------- 1 | -- This file is part of lua-rote, Lua binding to ROTE 2 | -- Terminal Emulation library 3 | -- Copyright (C) 2015 Boris Nagaev 4 | -- See the LICENSE file for terms of use. 5 | 6 | local function sleep() 7 | local duration = os.getenv('TEST_SLEEP') or 5 8 | os.execute('sleep ' .. duration) 9 | end 10 | 11 | describe("rote.RoteTerm", function() 12 | it("creates RoteTerm", function() 13 | local rote = assert(require "rote") 14 | local rt = rote.RoteTerm(24, 80) 15 | end) 16 | 17 | it("can be converted to a string", function() 18 | local rote = assert(require "rote") 19 | local rt = rote.RoteTerm(24, 80) 20 | local text = tostring(rt) 21 | assert.falsy(text:match("userdata")) 22 | end) 23 | 24 | it("throws if size is invalid", function() 25 | local rote = assert(require "rote") 26 | assert.has_error(function() 27 | local rt = rote.RoteTerm(0, 80) 28 | end) 29 | assert.has_error(function() 30 | local rt = rote.RoteTerm(24, 0) 31 | end) 32 | end) 33 | 34 | it("gets size of RoteTerm", function() 35 | local rote = assert(require "rote") 36 | local rt = rote.RoteTerm(24, 80) 37 | assert.equal(24, rt:rows()) 38 | assert.equal(80, rt:cols()) 39 | end) 40 | 41 | it("sets cursor to (0, 0) initially", function() 42 | local rote = assert(require "rote") 43 | local rt = rote.RoteTerm(24, 80) 44 | assert.equal(0, rt:row()) 45 | assert.equal(0, rt:col()) 46 | end) 47 | 48 | it("sets all chars to space initially", function() 49 | local rote = assert(require "rote") 50 | local rt = rote.RoteTerm(24, 80) 51 | for row = 0, 23 do 52 | for col = 0, 79 do 53 | assert.equal(' ', rt:cellChar(row, col)) 54 | end 55 | end 56 | end) 57 | 58 | it("gets one row", function() 59 | local rote = assert(require "rote") 60 | local rt = rote.RoteTerm(24, 80) 61 | rt:forkPty('echo 1234567890') 62 | sleep() 63 | rt:update() 64 | rt:forsakeChild() 65 | assert.truthy(rt:rowText(0):match("1234567890")) 66 | end) 67 | 68 | it("throws if row number if wrong", function() 69 | local rote = assert(require "rote") 70 | local rt = rote.RoteTerm(24, 80) 71 | assert.has_error(function() 72 | rt:rowText(-1) 73 | end) 74 | assert.has_error(function() 75 | rt:rowText(24) 76 | end) 77 | end) 78 | 79 | it("gets whole #screen", function() 80 | local rote = assert(require "rote") 81 | local rt = rote.RoteTerm(24, 80) 82 | local fname = os.tmpname() 83 | local f = io.open(fname, 'w') 84 | f:write("hello\nworld\ntest\n") 85 | f:close() 86 | local pid = rt:forkPty('less ' .. fname) 87 | sleep() 88 | rt:update() 89 | local text = rt:termText() 90 | assert.equal(24 * (80 + 1), #text) 91 | local breaks = 0 92 | for _ in string.gmatch(text, "\n") do 93 | breaks = breaks + 1 94 | end 95 | assert.equal(24, breaks) 96 | assert.truthy(text:match("hello")) 97 | assert.truthy(text:match("world")) 98 | assert.truthy(text:match("test")) 99 | os.execute('kill -9 ' .. pid) 100 | rt:forsakeChild() 101 | os.remove(fname) 102 | end) 103 | 104 | it("changes cell char", function() 105 | local rote = assert(require "rote") 106 | local rt = rote.RoteTerm(24, 80) 107 | rt:setCellChar(0, 0, 'A') 108 | assert.equal('A', rt:cellChar(0, 0)) 109 | end) 110 | 111 | it("throws if length(ch) != 1", function() 112 | assert.has_error(function() 113 | local rote = assert(require "rote") 114 | local rt = rote.RoteTerm(24, 80) 115 | rt:setCellChar(0, 0, 'aa') 116 | end) 117 | assert.has_error(function() 118 | local rote = assert(require "rote") 119 | local rt = rote.RoteTerm(24, 80) 120 | rt:setCellChar(0, 0, '') 121 | end) 122 | end) 123 | 124 | it("throws if ch is a control character", function() 125 | assert.has_error(function() 126 | local rote = assert(require "rote") 127 | local rt = rote.RoteTerm(24, 80) 128 | rt:setCellChar(0, 0, '\0') 129 | end) 130 | assert.has_error(function() 131 | local rote = assert(require "rote") 132 | local rt = rote.RoteTerm(24, 80) 133 | rt:setCellChar(0, 0, '\n') 134 | end) 135 | assert.has_error(function() 136 | local rote = assert(require "rote") 137 | local rt = rote.RoteTerm(24, 80) 138 | rt:setCellChar(0, 0, string.char(31)) 139 | end) 140 | end) 141 | 142 | it("changes cell attribute", function() 143 | local rote = assert(require "rote") 144 | local rt = rote.RoteTerm(24, 80) 145 | rt:setCellAttr(0, 0, 42) 146 | assert.equal(42, rt:cellAttr(0, 0)) 147 | end) 148 | 149 | it("throws if cell attribute is invalid", function() 150 | assert.has_error(function() 151 | local rote = assert(require "rote") 152 | local rt = rote.RoteTerm(24, 80) 153 | rt:setCellAttr(0, 0, -1) 154 | end) 155 | assert.has_error(function() 156 | local rote = assert(require "rote") 157 | local rt = rote.RoteTerm(24, 80) 158 | rt:setCellAttr(0, 0, 256) 159 | end) 160 | end) 161 | 162 | it("changes current attribute", function() 163 | local rote = assert(require "rote") 164 | local rt = rote.RoteTerm(24, 80) 165 | rt:setAttr(30) 166 | assert.equal(30, rt:attr()) 167 | end) 168 | 169 | it("has child_pid = 0 by default", function() 170 | local rote = assert(require "rote") 171 | local rt = rote.RoteTerm(24, 80) 172 | assert.equal(0, rt:childPid()) 173 | end) 174 | 175 | it("forks pty", function() 176 | local rote = assert(require "rote") 177 | local rt = rote.RoteTerm(24, 80) 178 | local pid = rt:forkPty('ls') 179 | assert.equal(pid, rt:childPid()) 180 | -- TODO fix zombie 181 | end) 182 | 183 | it("forsakes child", function() 184 | local rote = assert(require "rote") 185 | local rt = rote.RoteTerm(24, 80) 186 | rt:forkPty('ls') 187 | sleep() 188 | rt:forsakeChild() 189 | -- TODO fix zombie 190 | end) 191 | 192 | it("does not #leak screen size to child", function() 193 | local sizes_lua = [[ 194 | curses = require 'curses' 195 | stdscr = curses.initscr() 196 | win_rows, win_cols = stdscr:getmaxyx() 197 | curses.endwin() 198 | f = io.open(%q, 'w') 199 | f:write('win_rows=' .. win_rows .. 200 | ' win_cols=' .. win_cols .. '\n') 201 | f:close() 202 | ]] 203 | local out_fname = os.tmpname() 204 | sizes_lua = sizes_lua:format(out_fname) 205 | -- 206 | local sizes_lua_fname = os.tmpname() 207 | local f = io.open(sizes_lua_fname, 'w') 208 | f:write(sizes_lua) 209 | f:close() 210 | -- 211 | local rote_lua = [[ 212 | rote = require 'rote' 213 | rows = %i 214 | cols = %i 215 | rt = rote.RoteTerm(rows, cols) 216 | rt:forkPty('lua %s') 217 | os.execute('sleep 5') 218 | rt:update() 219 | rt:write('q') 220 | rt:update() 221 | rt:forsakeChild() 222 | ]] 223 | local rows = 4 224 | local cols = 5 225 | rote_lua = rote_lua:format(rows, cols, 226 | sizes_lua_fname) 227 | local rote_lua_fname = os.tmpname() 228 | local f = io.open(rote_lua_fname, 'w') 229 | f:write(rote_lua) 230 | f:close() 231 | -- 232 | local expected = 'win_rows=' .. rows .. 233 | ' win_cols=' .. cols 234 | -- 235 | os.execute('lua ' .. rote_lua_fname) 236 | local f = io.open(out_fname, 'r') 237 | local out = f:read('*a') 238 | f:close() 239 | assert.truthy(out:match(expected)) 240 | -- 241 | os.remove(out_fname) 242 | os.execute('cat ' .. rote_lua_fname .. ' | lua -i > /dev/null 2>&1') 243 | local f = io.open(out_fname, 'r') 244 | local out = f:read('*a') 245 | f:close() 246 | assert.truthy(out:match(expected)) 247 | -- 248 | os.remove(out_fname) 249 | os.remove(sizes_lua_fname) 250 | os.remove(rote_lua_fname) 251 | end) 252 | 253 | it("gets updates from child", function() 254 | local rote = assert(require "rote") 255 | local rt = rote.RoteTerm(24, 80) 256 | rt:forkPty('ls -a') 257 | sleep() 258 | rt:update() 259 | rt:forsakeChild() 260 | assert.not_equal(' ', rt:cellChar(0, 0)) 261 | end) 262 | 263 | local print_AB_then_CD = [[ 264 | curses = require 'curses' 265 | stdscr = curses.initscr() 266 | curses.echo(false) 267 | curses.start_color() 268 | curses.raw(true) 269 | curses.curs_set(0) 270 | stdscr:nodelay(false) 271 | stdscr:keypad(true) 272 | -- 273 | stdscr:move(0, 0) 274 | stdscr:addch(string.byte('A')) 275 | stdscr:move(0, 1) 276 | stdscr:addch(string.byte('B')) 277 | stdscr:refresh() 278 | -- 279 | stdscr:getch() 280 | -- 281 | stdscr:move(0, 0) 282 | stdscr:addch(string.byte('C')) 283 | stdscr:move(0, 1) 284 | stdscr:addch(string.byte('D')) 285 | stdscr:refresh() 286 | -- 287 | stdscr:getch() 288 | -- 289 | curses.endwin() 290 | ]] 291 | 292 | pending("reads output from #1x2 window", 293 | function() 294 | -- 295 | local app_lua_fname = os.tmpname() 296 | local f = io.open(app_lua_fname, 'w') 297 | f:write(print_AB_then_CD) 298 | f:close() 299 | -- 300 | local rote = require 'rote' 301 | rt = rote.RoteTerm(1, 2) 302 | local cmd = 'lua %s' 303 | cmd = cmd:format(app_lua_fname) 304 | rt:forkPty(cmd) 305 | -- 306 | sleep() 307 | rt:update() 308 | assert.equal('AB', rt:rowText(0)) 309 | -- 310 | rt:write(' ') 311 | sleep() 312 | rt:update() 313 | -- This assert fails VVVV (rowText = 'D ') 314 | assert.equal('CD', rt:rowText(0)) 315 | -- 316 | rt:write(' ') 317 | rt:update() 318 | rt:forsakeChild() 319 | -- 320 | os.remove(app_lua_fname) 321 | end) 322 | 323 | it("reads well from large window", 324 | function() 325 | -- 326 | local app_lua_fname = os.tmpname() 327 | local f = io.open(app_lua_fname, 'w') 328 | f:write(print_AB_then_CD) 329 | f:close() 330 | -- 331 | local rote = require 'rote' 332 | rt = rote.RoteTerm(24, 80) 333 | local cmd = 'lua %s' 334 | cmd = cmd:format(app_lua_fname) 335 | rt:forkPty(cmd) 336 | -- 337 | sleep() 338 | rt:update() 339 | assert.truthy(rt:rowText(0):match('AB')) 340 | -- 341 | rt:write(' ') 342 | sleep() 343 | rt:update() 344 | assert.truthy(rt:rowText(0):match('CD')) 345 | -- 346 | rt:write(' ') 347 | rt:update() 348 | rt:forsakeChild() 349 | -- 350 | os.remove(app_lua_fname) 351 | end) 352 | 353 | it("reads cell where #background=foreground from child", 354 | function() 355 | local app_lua = [[ 356 | curses = require 'curses' 357 | stdscr = curses.initscr() 358 | curses.echo(false) 359 | curses.start_color() 360 | curses.raw(true) 361 | curses.curs_set(0) 362 | stdscr:nodelay(false) 363 | stdscr:keypad(true) 364 | -- 365 | local function makePair(foreground, background) 366 | return background * 8 + 7 - foreground 367 | end 368 | -- 369 | for foreground = 0, 7 do 370 | for background = 0, 7 do 371 | if foreground ~= 7 or background ~= 0 then 372 | local pair = makePair(foreground, background) 373 | curses.init_pair(pair, foreground, background) 374 | end 375 | end 376 | end 377 | -- 378 | stdscr:move(0, 0) 379 | stdscr:attrset(curses.color_pair(makePair(0, 0))) 380 | stdscr:addch(string.byte('A')) 381 | stdscr:move(0, 1) 382 | stdscr:attrset(curses.color_pair(makePair(0, 7))) 383 | stdscr:addch(string.byte('B')) 384 | stdscr:refresh() 385 | -- 386 | stdscr:getch() 387 | -- 388 | stdscr:move(0, 0) 389 | stdscr:attrset(curses.color_pair(makePair(0, 0))) 390 | stdscr:addch(string.byte('C')) 391 | stdscr:move(0, 1) 392 | stdscr:attrset(curses.color_pair(makePair(0, 7))) 393 | stdscr:addch(string.byte('D')) 394 | stdscr:refresh() 395 | -- 396 | stdscr:getch() 397 | -- 398 | curses.endwin() 399 | ]] 400 | -- 401 | local app_lua_fname = os.tmpname() 402 | local f = io.open(app_lua_fname, 'w') 403 | f:write(app_lua) 404 | f:close() 405 | -- 406 | local rote = require 'rote' 407 | rt = rote.RoteTerm(24, 80) 408 | local cmd = 'lua %s' 409 | cmd = cmd:format(app_lua_fname) 410 | rt:forkPty(cmd) 411 | -- 412 | sleep() 413 | rt:update() 414 | assert.truthy(rt:rowText(0):match('AB')) 415 | -- 416 | rt:write(' ') 417 | sleep() 418 | rt:update() 419 | assert.truthy(rt:rowText(0):match('CD')) 420 | -- 421 | rt:write(' ') 422 | rt:update() 423 | rt:forsakeChild() 424 | -- 425 | os.remove(app_lua_fname) 426 | end) 427 | 428 | it("restores from snapshot", function() 429 | local rote = assert(require "rote") 430 | local rt = rote.RoteTerm(24, 80) 431 | local snapshot = rt:takeSnapshot() 432 | assert.equal(' ', rt:cellChar(0, 0)) 433 | rt:setCellChar(0, 0, 'A') 434 | assert.equal('A', rt:cellChar(0, 0)) 435 | rt:restoreSnapshot(snapshot) 436 | assert.equal(' ', rt:cellChar(0, 0)) 437 | end) 438 | 439 | it("returns pseudo tty descriptor", function() 440 | local rote = assert(require "rote") 441 | local rt = rote.RoteTerm(24, 80) 442 | assert.equal(-1, rt:getPtyFd()) 443 | local pid = rt:forkPty('vi') 444 | assert.not_equal(-1, rt:getPtyFd()) 445 | rt:update() 446 | os.execute('kill -9 ' .. pid) 447 | rt:forsakeChild() 448 | assert.equal(-1, rt:getPtyFd()) 449 | end) 450 | 451 | it("edits text file with #vi", function() 452 | local text = 'secret' 453 | local filename = os.tmpname() 454 | local esc = '\27' 455 | local enter = '\n' 456 | -- 457 | local rote = assert(require "rote") 458 | local wait = assert(require "posix.sys.wait") 459 | local rt = rote.RoteTerm(3, 20) 460 | local pid = rt:forkPty('vi ' .. filename) 461 | sleep() 462 | rt:update() 463 | rt:write('i' .. text .. esc .. ':wq') 464 | rt:keyPress(string.byte(enter)) -- test keyPress() 465 | wait.wait(pid) 466 | rt:update() 467 | rt:forsakeChild() 468 | -- 469 | local f = io.open(filename, 'r') 470 | local text_in_file = f:read('*a'):gsub('%s', '') 471 | f:close() 472 | os.remove(filename) 473 | assert.equal(text, text_in_file) 474 | end) 475 | 476 | it("moves UP and DOWN in #less", function() 477 | local text = '1\n2\n3\n4\n5\n6\n7\n8\n' 478 | local filename = os.tmpname() 479 | local f = io.open(filename, 'w') 480 | f:write(text) 481 | f:close() 482 | local esc = '\27' 483 | local up = esc .. '[A' 484 | local down = esc .. '[B' 485 | local right = esc .. '[C' 486 | local left = esc .. '[D' 487 | -- 488 | local rote = assert(require "rote") 489 | local rt = rote.RoteTerm(3, 20) 490 | rt:forkPty('less ' .. filename) 491 | sleep() 492 | rt:update() 493 | assert.truthy(rt:termText():match('1')) 494 | assert.truthy(rt:termText():match('2')) 495 | -- 496 | rt:write(down) 497 | sleep() 498 | rt:update() 499 | assert.falsy(rt:termText():match('1')) 500 | assert.truthy(rt:termText():match('2')) 501 | assert.truthy(rt:termText():match('3')) 502 | -- 503 | rt:write(down .. down) 504 | sleep() 505 | rt:update() 506 | assert.falsy(rt:termText():match('1')) 507 | assert.falsy(rt:termText():match('2')) 508 | assert.falsy(rt:termText():match('3')) 509 | assert.truthy(rt:termText():match('4')) 510 | assert.truthy(rt:termText():match('5')) 511 | -- 512 | rt:write(right .. right) 513 | sleep() 514 | rt:update() 515 | assert.falsy(rt:termText():match('1')) 516 | assert.falsy(rt:termText():match('2')) 517 | assert.falsy(rt:termText():match('3')) 518 | assert.falsy(rt:termText():match('4')) 519 | assert.falsy(rt:termText():match('5')) 520 | -- 521 | rt:write(up .. up .. up .. left .. left) 522 | sleep() 523 | rt:update() 524 | assert.truthy(rt:termText():match('1')) 525 | assert.truthy(rt:termText():match('2')) 526 | -- 527 | rt:write('q') 528 | rt:update() 529 | rt:forsakeChild() 530 | -- 531 | os.remove(filename) 532 | end) 533 | 534 | it("#injects characters into terminal", function() 535 | local rote = assert(require "rote") 536 | local rt = rote.RoteTerm(24, 80) 537 | rt:inject('secret') 538 | assert.truthy(rt:rowText(0):match('secret')) 539 | end) 540 | end) 541 | -------------------------------------------------------------------------------- /spec/attr_spec.lua: -------------------------------------------------------------------------------- 1 | -- This file is part of lua-rote, Lua binding to ROTE 2 | -- Terminal Emulation library 3 | -- Copyright (C) 2015 Boris Nagaev 4 | -- See the LICENSE file for terms of use. 5 | 6 | describe("rote.attr", function() 7 | it("packs and unpacks attributes (example)", function() 8 | local rote = assert(require "rote") 9 | local n2c = rote.name2color 10 | local fg = n2c.green 11 | local bg = n2c.red 12 | local bold = true 13 | local blink = false 14 | local attr = rote.toAttr(fg, bg, bold, blink) 15 | assert.truthy(attr >= 0) 16 | assert.truthy(attr < 256) 17 | local fg1, bg1, bold1, blink1 = rote.fromAttr(attr) 18 | assert.equal(fg, fg1) 19 | assert.equal(bg, bg1) 20 | assert.equal(bold, bold1) 21 | assert.equal(blink, blink1) 22 | end) 23 | 24 | it("can be applied to all possible values of attribute", 25 | function() 26 | local rote = assert(require "rote") 27 | for attr = 0, 255 do 28 | local fg, bg, bold, blink = rote.fromAttr(attr) 29 | local attr1 = rote.toAttr(fg, bg, bold, blink) 30 | assert.equal(attr, attr1) 31 | end 32 | end) 33 | 34 | it("defaults to bold=false and blink=false", function() 35 | local rote = assert(require "rote") 36 | assert.equal(rote.toAttr(1, 2, false, false), 37 | rote.toAttr(1, 2)) 38 | end) 39 | 40 | it("throws on bad attr", function() 41 | local rote = assert(require "rote") 42 | assert.has_error(function() 43 | local fg, bg, bold, blink = rote.fromAttr(-1) 44 | end) 45 | assert.has_error(function() 46 | local fg, bg, bold, blink = rote.fromAttr(256) 47 | end) 48 | end) 49 | 50 | it("throws on bad color", function() 51 | local rote = assert(require "rote") 52 | assert.has_error(function() 53 | local attr = rote.toAttr(-1, 1) 54 | end) 55 | assert.has_error(function() 56 | local attr = rote.toAttr(8, 1) 57 | end) 58 | assert.has_error(function() 59 | local attr = rote.toAttr(1, -1) 60 | end) 61 | assert.has_error(function() 62 | local attr = rote.toAttr(1, 8) 63 | end) 64 | end) 65 | end) 66 | -------------------------------------------------------------------------------- /spec/boxshell_spec.lua: -------------------------------------------------------------------------------- 1 | -- This file is part of lua-rote, Lua binding to ROTE 2 | -- Terminal Emulation library 3 | -- Copyright (C) 2015 Boris Nagaev 4 | -- See the LICENSE file for terms of use. 5 | 6 | -- test RoteTerm:draw() and script demo/boxshell.lua 7 | 8 | -- FIXME RoteTerm:draw() draws wrong things on Travis 9 | -- https://travis-ci.org/starius/lua-rote/jobs/54479120#L1160 10 | 11 | local function sleep() 12 | local duration = os.getenv('TEST_SLEEP') or 5 13 | os.execute('sleep ' .. duration) 14 | end 15 | 16 | describe("rote.RoteTerm.draw", function() 17 | it("draws the terminal to the #curses window", function() 18 | -- create file with secret text 19 | local secret = 'secret' 20 | local filename = os.tmpname() 21 | local f = io.open(filename, 'w') 22 | f:write(secret) 23 | f:close() 24 | -- create RoteTerm, run boxshell.lua in RoteTerm 25 | local rote = assert(require "rote") 26 | local rt = rote.RoteTerm(24, 80) 27 | rt:forkPty('lua -lluacov demo/boxshell.lua vi') 28 | sleep() 29 | rt:update() 30 | assert.truthy(rt:termText():match('Term In a Box')) 31 | -- cell (0, 0) must have blue background 32 | local attr = rt:cellAttr(0, 0) 33 | local fg, bg = rote.fromAttr(attr) 34 | assert.equal(rote.name2color.blue, bg) 35 | -- open file 36 | local cmd = ':e %s\n' 37 | rt:write(cmd:format(filename)) 38 | sleep() 39 | rt:update() 40 | -- FIXME RoteTerm:draw() draws wrong things on Travis 41 | --assert.truthy(rt:termText():match(secret)) 42 | -- quit 43 | rt:write(':q\n') 44 | sleep() 45 | rt:update() 46 | rt:forsakeChild() 47 | -- cleanup 48 | os.remove(filename) 49 | end) 50 | end) 51 | -------------------------------------------------------------------------------- /spec/color_spec.lua: -------------------------------------------------------------------------------- 1 | -- This file is part of lua-rote, Lua binding to ROTE 2 | -- Terminal Emulation library 3 | -- Copyright (C) 2015 Boris Nagaev 4 | -- See the LICENSE file for terms of use. 5 | 6 | describe("rote.color", function() 7 | it("converts colors to/from integers", function() 8 | local rote = assert(require "rote") 9 | local c2n = rote.color2name 10 | local n2c = rote.name2color 11 | assert.equal("green", c2n[n2c.green]) 12 | assert.equal(2, n2c[c2n[2]]) 13 | end) 14 | end) 15 | -------------------------------------------------------------------------------- /spec/cursesConsts_spec.lua: -------------------------------------------------------------------------------- 1 | -- This file is part of lua-rote, Lua binding to ROTE 2 | -- Terminal Emulation library 3 | -- Copyright (C) 2015 Boris Nagaev 4 | -- See the LICENSE file for terms of use. 5 | 6 | describe("rote.cursesConsts", function() 7 | it("gets values of curses constants", function() 8 | local cursesConsts = require 'rote.cursesConsts' 9 | assert.truthy(cursesConsts.KEY_UP) 10 | assert.truthy(cursesConsts.KEY_DOWN) 11 | assert.truthy(cursesConsts.KEY_LEFT) 12 | assert.truthy(cursesConsts.KEY_RIGHT) 13 | end) 14 | end) 15 | -------------------------------------------------------------------------------- /spec/load_module_spec.lua: -------------------------------------------------------------------------------- 1 | -- This file is part of lua-rote, Lua binding to ROTE 2 | -- Terminal Emulation library 3 | -- Copyright (C) 2015 Boris Nagaev 4 | -- See the LICENSE file for terms of use. 5 | 6 | describe("rote", function() 7 | it("loads rote module", function() 8 | local rote = assert(require "rote") 9 | end) 10 | end) 11 | -------------------------------------------------------------------------------- /src/attr.c: -------------------------------------------------------------------------------- 1 | // lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | // Copyright (C) 2015 Boris Nagaev 3 | // See the LICENSE file for terms of use. 4 | 5 | // ROTE is a simple C library for VT102 terminal emulation. 6 | // See http://rote.sourceforge.net/ 7 | 8 | #include "lua-rote.h" 9 | 10 | // converts foreground, background colors to attribute 11 | // agrument 1: int foreground 12 | // agrument 2: int background 13 | // agrument 3: boolean bold (optinal) 14 | // agrument 4: boolean blink (optinal) 15 | int lua_toAttr(lua_State *L) { 16 | int foreground = luaL_checkinteger(L, 1); 17 | int background = luaL_checkinteger(L, 2); 18 | int bold = lua_toboolean(L, 3); 19 | int blink = lua_toboolean(L, 4); 20 | luaL_argcheck(L, foreground >= 0, 1, "foreground >= 0"); 21 | luaL_argcheck(L, foreground < 8, 2, "foreground < 8"); 22 | luaL_argcheck(L, background >= 0, 3, "background >= 0"); 23 | luaL_argcheck(L, background < 8, 4, "background < 8"); 24 | unsigned char ch = 0; 25 | ROTE_ATTR_MOD_FG(ch, foreground); 26 | ROTE_ATTR_MOD_BG(ch, background); 27 | ROTE_ATTR_MOD_BOLD(ch, bold); 28 | ROTE_ATTR_MOD_BLINK(ch, blink); 29 | lua_pushinteger(L, ch); 30 | return 1; 31 | } 32 | 33 | // converts attribute to foreground, background colors 34 | // agrument 1: int attribute 35 | // output 1: int foreground 36 | // output 2: int background 37 | // output 3: boolean bold 38 | // output 4: boolean blink 39 | int lua_fromAttr(lua_State *L) { 40 | int attr = lua_getAttr(L, 1); 41 | lua_pushinteger(L, ROTE_ATTR_FG(attr)); 42 | lua_pushinteger(L, ROTE_ATTR_BG(attr)); 43 | lua_pushboolean(L, ROTE_ATTR_BOLD(attr)); 44 | lua_pushboolean(L, ROTE_ATTR_BLINK(attr)); 45 | return 4; 46 | } 47 | -------------------------------------------------------------------------------- /src/color.c: -------------------------------------------------------------------------------- 1 | // lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | // Copyright (C) 2015 Boris Nagaev 3 | // See the LICENSE file for terms of use. 4 | 5 | // ROTE is a simple C library for VT102 terminal emulation. 6 | // See http://rote.sourceforge.net/ 7 | 8 | #include "lua-rote.h" 9 | 10 | // argument -2 is table name => int 11 | // argument -1 is table int => name 12 | static void register_color(lua_State *L, 13 | const char* name, int value) { 14 | // table name => int 15 | lua_pushinteger(L, value); 16 | lua_setfield(L, -3, name); 17 | // table int => name 18 | lua_pushinteger(L, value); 19 | lua_pushstring(L, name); 20 | lua_settable(L, -3); 21 | } 22 | 23 | // register standard colors 24 | void register_colors(lua_State *L) { 25 | lua_newtable(L); // rote.name2color 26 | lua_newtable(L); // rote.color2name 27 | // 28 | register_color(L, "black", 0); 29 | register_color(L, "red", 1); 30 | register_color(L, "green", 2); 31 | register_color(L, "yellow", 3); 32 | register_color(L, "blue", 4); 33 | register_color(L, "magenta", 5); 34 | register_color(L, "cyan", 6); 35 | register_color(L, "white", 7); 36 | // 37 | lua_setfield(L, -3, "color2name"); 38 | lua_setfield(L, -2, "name2color"); 39 | } 40 | -------------------------------------------------------------------------------- /src/cursesConsts.lua: -------------------------------------------------------------------------------- 1 | -- lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | -- Copyright (C) 2015 Boris Nagaev 3 | -- See the LICENSE file for terms of use. 4 | 5 | -- ROTE is a simple C library for VT102 terminal emulation. 6 | -- See http://rote.sourceforge.net/ 7 | 8 | -- returns table of curses numeric consts 9 | -- module posix.curses requires starting curses, 10 | -- otherwise these consts are not initialized. 11 | -- Starting curses if often not desirable. 12 | -- This module spawns child process, which 13 | -- prints values of consts. 14 | 15 | local rote = require 'rote' 16 | local wait = assert(require "posix.sys.wait") 17 | 18 | local function printConsts() 19 | local curses = require 'curses' 20 | local _ = curses.initscr() 21 | curses.endwin() 22 | local out_fname = assert(arg[1]) 23 | local out = io.open(out_fname, 'w') 24 | out:write('return {') 25 | for name, value in pairs(curses) do 26 | if type(value) == "number" then 27 | local t = "[%q] = %i," 28 | out:write(t:format(name, value)) 29 | end 30 | end 31 | out:write('}') 32 | out:close() 33 | end 34 | 35 | local fname = os.tmpname() 36 | local f = io.open(fname, 'w') 37 | f:write(string.dump(printConsts)) 38 | f:close() 39 | 40 | local fname2 = os.tmpname() 41 | 42 | local lluacov = os.getenv('LOAD_LUACOV') or '' 43 | 44 | local cmd = 'lua %s %s %s' 45 | cmd = cmd:format(lluacov, fname, fname2) 46 | 47 | local rt = rote.RoteTerm(24, 80) 48 | local pid = rt:forkPty(cmd) 49 | wait.wait(pid) 50 | rt:forsakeChild() 51 | 52 | local consts = dofile(fname2) 53 | 54 | os.remove(fname) 55 | os.remove(fname2) 56 | 57 | return consts 58 | -------------------------------------------------------------------------------- /src/lua-rote.c: -------------------------------------------------------------------------------- 1 | // lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | // Copyright (C) 2015 Boris Nagaev 3 | // See the LICENSE file for terms of use. 4 | 5 | // ROTE is a simple C library for VT102 terminal emulation. 6 | // See http://rote.sourceforge.net/ 7 | 8 | #include "lua-rote.h" 9 | 10 | static const luaL_Reg rote_funcs[] = { 11 | {"RoteTerm", lua_RoteTerm}, 12 | {"toAttr", lua_toAttr}, 13 | {"fromAttr", lua_fromAttr}, 14 | {NULL, NULL} 15 | }; 16 | 17 | int luaopen_rote(lua_State *L) { 18 | register_types(L); 19 | lua_newtable(L); // module "rote" 20 | my_setfuncs(L, rote_funcs); 21 | register_colors(L); 22 | return 1; 23 | } 24 | -------------------------------------------------------------------------------- /src/lua-rote.h: -------------------------------------------------------------------------------- 1 | // lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | // Copyright (C) 2015 Boris Nagaev 3 | // See the LICENSE file for terms of use. 4 | 5 | // ROTE is a simple C library for VT102 terminal emulation. 6 | // See http://rote.sourceforge.net/ 7 | 8 | #ifndef LUA_ROTE_H_ 9 | #define LUA_ROTE_H_ 10 | 11 | #include 12 | #include 13 | #define LUA_LIB 14 | #include 15 | #include 16 | #include 17 | 18 | #if LUA_VERSION_NUM == 501 19 | #define my_setfuncs(L, funcs) luaL_register(L, NULL, funcs) 20 | #else 21 | #define my_setfuncs(L, funcs) luaL_setfuncs(L, funcs, 0) 22 | #endif 23 | 24 | // converts foreground, background colors to attribute 25 | int lua_toAttr(lua_State *L); 26 | 27 | // converts attribute to foreground, background colors 28 | int lua_fromAttr(lua_State *L); 29 | 30 | // gets and checks attribute at given index 31 | int lua_getAttr(lua_State* L, int index); 32 | 33 | // register standard colors 34 | // argument -2 is table name => int 35 | // argument -1 is table int => name 36 | void register_colors(lua_State *L); 37 | 38 | // create instance of RoteTerm 39 | int lua_RoteTerm(lua_State* L); 40 | 41 | /** Get curses WINDOW from a userdatum at given index. 42 | A userdatum stores a pointer to WINDOW. 43 | Metatable of the userdatum must be "curses:window". 44 | See https://github.com/lcurses/lcurses/blob/master/ext/curses/window.c 45 | */ 46 | WINDOW* lua_getWindow(lua_State* L, int index); 47 | 48 | // create metatables of types, add them to the registry 49 | void register_types(lua_State* L); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/ncurses.c: -------------------------------------------------------------------------------- 1 | // lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | // Copyright (C) 2015 Boris Nagaev 3 | // See the LICENSE file for terms of use. 4 | 5 | // ROTE is a simple C library for VT102 terminal emulation. 6 | // See http://rote.sourceforge.net/ 7 | 8 | #include "lua-rote.h" 9 | 10 | WINDOW* lua_getWindow(lua_State* L, int index) { 11 | WINDOW** win = luaL_checkudata(L, index, 12 | "curses:window"); 13 | return *win; 14 | } 15 | -------------------------------------------------------------------------------- /src/rote.c: -------------------------------------------------------------------------------- 1 | // lua-rote, Lua binding to ROTE, Terminal Emulation library 2 | // Copyright (C) 2015 Boris Nagaev 3 | // See the LICENSE file for terms of use. 4 | 5 | // ROTE is a simple C library for VT102 terminal emulation. 6 | // See http://rote.sourceforge.net/ 7 | 8 | #include 9 | 10 | #include "lua-rote.h" 11 | 12 | static RoteTerm* lua_RoteTerm_self(lua_State* L, int index) { 13 | RoteTerm** rt = luaL_checkudata(L, index, "rote_RoteTerm"); 14 | return *rt; 15 | } 16 | 17 | static int lua_RoteTerm_gc(lua_State* L) { 18 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 19 | rote_vt_destroy(rt); 20 | return 0; 21 | } 22 | 23 | static int lua_RoteTerm_tostring(lua_State* L) { 24 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 25 | char text[500]; 26 | int length = sprintf(text, "RoteTerm %ix%i", 27 | rt->rows, rt->cols); 28 | lua_pushlstring(L, text, length); 29 | return 1; 30 | } 31 | 32 | static int lua_RoteTerm_rows(lua_State* L) { 33 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 34 | lua_pushinteger(L, rt->rows); 35 | return 1; 36 | } 37 | 38 | static int lua_RoteTerm_cols(lua_State* L) { 39 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 40 | lua_pushinteger(L, rt->cols); 41 | return 1; 42 | } 43 | 44 | static int lua_RoteTerm_row(lua_State* L) { 45 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 46 | lua_pushinteger(L, rt->crow); 47 | return 1; 48 | } 49 | 50 | static int lua_RoteTerm_col(lua_State* L) { 51 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 52 | lua_pushinteger(L, rt->ccol); 53 | return 1; 54 | } 55 | 56 | static void lua_RoteTerm_row_col(lua_State* L, 57 | RoteTerm* rt, int* row, int* col) { 58 | *row = luaL_checkinteger(L, 2); 59 | *col = luaL_checkinteger(L, 3); 60 | luaL_argcheck(L, *row >= 0, 2, "row >= 0"); 61 | luaL_argcheck(L, *row < rt->rows, 2, "row < rows"); 62 | luaL_argcheck(L, *col >= 0, 3, "col >= 0"); 63 | luaL_argcheck(L, *col < rt->cols, 3, "col < cols"); 64 | } 65 | 66 | // arguments: 67 | // 1. RowTerm 68 | // 2. int row 69 | // 3. int col 70 | static int lua_RoteTerm_cellChar(lua_State* L) { 71 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 72 | int row, col; 73 | lua_RoteTerm_row_col(L, rt, &row, &col); 74 | const char c = rt->cells[row][col].ch; 75 | lua_pushlstring(L, &c, 1); 76 | return 1; 77 | } 78 | 79 | // arguments: 80 | // 1. RowTerm 81 | // 2. int row 82 | // 3. int col 83 | // 4. string ch 84 | static int lua_RoteTerm_setCellChar(lua_State* L) { 85 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 86 | int row, col; 87 | lua_RoteTerm_row_col(L, rt, &row, &col); 88 | size_t length; 89 | const char* ch = luaL_checklstring(L, 4, &length); 90 | luaL_argcheck(L, length == 1, 4, "length(ch) == 1"); 91 | char c = ch[0]; 92 | luaL_argcheck(L, c >= 32, 4, "ch >= 32"); 93 | rt->cells[row][col].ch = c; 94 | return 0; 95 | } 96 | 97 | // arguments: 98 | // 1. RowTerm 99 | // 2. int row 100 | // 3. int col 101 | static int lua_RoteTerm_cellAttr(lua_State* L) { 102 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 103 | int row, col; 104 | lua_RoteTerm_row_col(L, rt, &row, &col); 105 | lua_pushinteger(L, rt->cells[row][col].attr); 106 | return 1; 107 | } 108 | 109 | int lua_getAttr(lua_State* L, int index) { 110 | int attr = luaL_checkinteger(L, index); 111 | luaL_argcheck(L, attr >= 0, index, "attr >= 0"); 112 | luaL_argcheck(L, attr < 256, index, "attr < 256"); 113 | return attr; 114 | } 115 | 116 | // arguments: 117 | // 1. RowTerm 118 | // 2. int row 119 | // 3. int col 120 | // 4. int attr 121 | static int lua_RoteTerm_setCellAttr(lua_State* L) { 122 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 123 | int row, col; 124 | lua_RoteTerm_row_col(L, rt, &row, &col); 125 | int attr = lua_getAttr(L, 4); 126 | rt->cells[row][col].attr = attr; 127 | return 0; 128 | } 129 | 130 | static int lua_RoteTerm_attr(lua_State* L) { 131 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 132 | lua_pushinteger(L, rt->curattr); 133 | return 1; 134 | } 135 | 136 | // arguments: 137 | // 1. RowTerm 138 | // 2. int attr 139 | static int lua_RoteTerm_setAttr(lua_State* L) { 140 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 141 | int attr = lua_getAttr(L, 2); 142 | rt->curattr = attr; 143 | return 0; 144 | } 145 | 146 | // arguments: 147 | // 1. RowTerm 148 | // 2. int row 149 | static int lua_RoteTerm_rowText(lua_State* L) { 150 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 151 | int row = luaL_checkinteger(L, 2); 152 | luaL_argcheck(L, row >= 0, 2, "row >= 0"); 153 | luaL_argcheck(L, row < rt->rows, 2, "row < rows"); 154 | char* buffer = lua_newuserdata(L, rt->cols); 155 | int col; 156 | RoteCell* cells = rt->cells[row]; 157 | for (col = 0; col < rt->cols; col++) { 158 | buffer[col] = cells[col].ch; 159 | } 160 | lua_pushlstring(L, buffer, rt->cols); 161 | return 1; 162 | } 163 | 164 | static int lua_RoteTerm_termText(lua_State* L) { 165 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 166 | int size = rt->rows * (rt->cols + 1); 167 | char* buffer = lua_newuserdata(L, size); 168 | int row; 169 | for (row = 0; row < rt->rows; row++) { 170 | char* b1 = buffer + row * (rt->cols + 1); 171 | int col; 172 | RoteCell* cells = rt->cells[row]; 173 | for (col = 0; col < rt->cols; col++) { 174 | b1[col] = cells[col].ch; 175 | } 176 | b1[rt->cols] = '\n'; 177 | } 178 | lua_pushlstring(L, buffer, size); 179 | return 1; 180 | } 181 | 182 | static int lua_RoteTerm_childPid(lua_State* L) { 183 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 184 | lua_pushinteger(L, rt->childpid); 185 | return 1; 186 | } 187 | 188 | // arguments: 189 | // 1. RowTerm 190 | // 2. command 191 | // returns: 192 | // 1. pid 193 | static int lua_RoteTerm_forkPty(lua_State* L) { 194 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 195 | size_t command_len; 196 | const char* command = luaL_checklstring(L, 2, 197 | &command_len); 198 | size_t size = command_len + 100; 199 | char* cmd = lua_newuserdata(L, size); 200 | sprintf(cmd, "LINES=%i COLUMNS=%i %s", 201 | rt->rows, rt->cols, command); 202 | int pid = rote_vt_forkpty(rt, cmd); 203 | lua_pushinteger(L, pid); 204 | return 1; 205 | } 206 | 207 | static int lua_RoteTerm_forsakeChild(lua_State* L) { 208 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 209 | rote_vt_forsake_child(rt); 210 | return 0; 211 | } 212 | 213 | static int lua_RoteTerm_update(lua_State* L) { 214 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 215 | rote_vt_update(rt); 216 | return 0; 217 | } 218 | 219 | // arguments: 220 | // 1. RowTerm 221 | // 2. string data 222 | static int lua_RoteTerm_write(lua_State* L) { 223 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 224 | size_t length; 225 | const char* data = luaL_checklstring(L, 2, &length); 226 | rote_vt_write(rt, data, length); 227 | return 0; 228 | } 229 | 230 | // arguments: 231 | // 1. RowTerm 232 | // 2. string data 233 | static int lua_RoteTerm_inject(lua_State* L) { 234 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 235 | size_t length; 236 | const char* data = luaL_checklstring(L, 2, &length); 237 | rote_vt_inject(rt, data, length); 238 | return 0; 239 | } 240 | 241 | // arguments: 242 | // 1. RowTerm 243 | // 2. WINDOW 244 | // 3. int start_row 245 | // 3. int start_col 246 | static int lua_RoteTerm_draw(lua_State* L) { 247 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 248 | WINDOW* win = lua_getWindow(L, 2); 249 | int start_row = luaL_checkinteger(L, 3); 250 | int start_col = luaL_checkinteger(L, 4); 251 | rote_vt_draw(rt, win, start_row, start_col, NULL); 252 | return 0; 253 | } 254 | 255 | // arguments: 256 | // 1. RowTerm 257 | // 2. int keycode 258 | static int lua_RoteTerm_keyPress(lua_State* L) { 259 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 260 | int keycode = luaL_checkinteger(L, 2); 261 | rote_vt_keypress(rt, keycode); 262 | return 0; 263 | } 264 | 265 | static void* lua_RoteSnapshot_self( 266 | lua_State* L, int index) { 267 | void** rs = luaL_checkudata(L, 268 | index, "rote_RoteSnapshot"); 269 | return *rs; 270 | } 271 | 272 | static int lua_RoteSnapshot_gc(lua_State* L) { 273 | void* rs = lua_RoteSnapshot_self(L, 1); 274 | free(rs); 275 | return 0; 276 | } 277 | 278 | static int lua_RoteTerm_takeSnapshot(lua_State* L) { 279 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 280 | void** rs = lua_newuserdata(L, sizeof(void*)); 281 | luaL_getmetatable(L, "rote_RoteSnapshot"); 282 | assert(lua_type(L, -1) == LUA_TTABLE); 283 | lua_setmetatable(L, -2); 284 | (*rs) = rote_vt_take_snapshot(rt); 285 | return 1; 286 | } 287 | 288 | static int lua_RoteTerm_restoreSnapshot(lua_State* L) { 289 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 290 | void* rs = lua_RoteSnapshot_self(L, 2); 291 | rote_vt_restore_snapshot(rt, rs); 292 | return 0; 293 | } 294 | 295 | static int lua_RoteTerm_getPtyFd(lua_State* L) { 296 | RoteTerm* rt = lua_RoteTerm_self(L, 1); 297 | int fd = rote_vt_get_pty_fd(rt); 298 | lua_pushinteger(L, fd); 299 | return 1; 300 | } 301 | 302 | // 303 | 304 | static const luaL_Reg RoteTerm_mt[] = { 305 | {"__gc", lua_RoteTerm_gc}, 306 | {"__tostring", lua_RoteTerm_tostring}, 307 | {"rows", lua_RoteTerm_rows}, 308 | {"cols", lua_RoteTerm_cols}, 309 | {"row", lua_RoteTerm_row}, 310 | {"col", lua_RoteTerm_col}, 311 | {"cellChar", lua_RoteTerm_cellChar}, 312 | {"setCellChar", lua_RoteTerm_setCellChar}, 313 | {"cellAttr", lua_RoteTerm_cellAttr}, 314 | {"setCellAttr", lua_RoteTerm_setCellAttr}, 315 | {"attr", lua_RoteTerm_attr}, 316 | {"setAttr", lua_RoteTerm_setAttr}, 317 | {"rowText", lua_RoteTerm_rowText}, 318 | {"termText", lua_RoteTerm_termText}, 319 | {"childPid", lua_RoteTerm_childPid}, 320 | {"forkPty", lua_RoteTerm_forkPty}, 321 | {"forsakeChild", lua_RoteTerm_forsakeChild}, 322 | {"update", lua_RoteTerm_update}, 323 | {"write", lua_RoteTerm_write}, 324 | {"inject", lua_RoteTerm_inject}, 325 | {"draw", lua_RoteTerm_draw}, 326 | {"keyPress", lua_RoteTerm_keyPress}, 327 | {"takeSnapshot", lua_RoteTerm_takeSnapshot}, 328 | {"restoreSnapshot", lua_RoteTerm_restoreSnapshot}, 329 | {"getPtyFd", lua_RoteTerm_getPtyFd}, 330 | {NULL, NULL} 331 | }; 332 | 333 | static const luaL_Reg RoteSnapshot_mt[] = { 334 | {"__gc", lua_RoteSnapshot_gc}, 335 | {NULL, NULL} 336 | }; 337 | 338 | // create metatables of types, add them to the registry 339 | void register_types(lua_State* L) { 340 | // metatable of RoteTerm 341 | luaL_newmetatable(L, "rote_RoteTerm"); 342 | my_setfuncs(L, RoteTerm_mt); 343 | lua_pushvalue(L, -1); 344 | lua_setfield(L, -2, "__index"); // mt.__index = mt 345 | lua_pop(L, 1); 346 | // metatable of RoteSnapshot 347 | luaL_newmetatable(L, "rote_RoteSnapshot"); 348 | my_setfuncs(L, RoteSnapshot_mt); 349 | lua_pushvalue(L, -1); 350 | lua_setfield(L, -2, "__index"); // mt.__index = mt 351 | lua_pop(L, 1); 352 | } 353 | 354 | // create instance of RoteTerm 355 | // arguments: 356 | // 1. int rows 357 | // 2. int cols 358 | int lua_RoteTerm(lua_State* L) { 359 | int rows = luaL_checkinteger(L, 1); 360 | int cols = luaL_checkinteger(L, 2); 361 | luaL_argcheck(L, rows > 0, 1, "rows > 0"); 362 | luaL_argcheck(L, cols > 0, 2, "cols > 0"); 363 | RoteTerm** rt = lua_newuserdata(L, sizeof(RoteTerm*)); 364 | luaL_getmetatable(L, "rote_RoteTerm"); 365 | assert(lua_type(L, -1) == LUA_TTABLE); 366 | lua_setmetatable(L, -2); 367 | (*rt) = rote_vt_create(rows, cols); 368 | return 1; 369 | } 370 | --------------------------------------------------------------------------------