├── .gitignore ├── LICENSE ├── README.md ├── c_src ├── cecho.c ├── cecho.h └── cecho_commands.h ├── examples.escript ├── helloworld.escript ├── include ├── cecho.hrl └── cecho_commands.hrl ├── rebar.config ├── rebar3 └── src ├── cecho.app.src ├── cecho.erl ├── cecho_example.erl └── cecho_srv.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | ebin 3 | priv 4 | c_src/*.o 5 | c_src/*.d 6 | _build 7 | rebar.lock 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Mazen Harake 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cecho 2 | An ncurses library for Erlang 3 | 4 | ## Introduction 5 | Cecho is an ncurses library for Erlang which enabled Erlang applications to create terminal based GUIs. It aims to have an API as close as possible to the original API of ncurses so that a person familiar with ncurses API should be able to immediately use it without any introduction. 6 | 7 | 8 | ## Compile 9 | To clean/compile run 10 | 11 | ./rebar3 clean 12 | ./rebar3 compile 13 | 14 | ## Usage 15 | The intention of this library is to have an API as close as possible to the ncurses library. Something things can't be translated and other things return values instead of taking pointer arguments etc. but overall the API should be similar enough to make use of the ncurses documentation that is out on the web. 16 | 17 | The following sequence is more or less standard when using the library: 18 | 1) Start the cecho application 19 | 2) Set flags and attributes, create windows and initialize data 20 | 3) Move cursor, add text and refresh window 21 | 3.1) Listen for input to be able to terminate the program 22 | 4) Stop the cecho application gracefully 23 | 24 | The following is an example hello world which bounces "Hello world" across the screen and terminates when 'q' is pressed. It can be found in the cecho_example.erl file in the src/ directory. 25 | 26 | %% 27 | %% helloworld - bounce "Hello World!" on the end of the screen 28 | %% 29 | helloworld() -> 30 | %% Start application 31 | application:start(cecho), 32 | %% Set attributes 33 | cecho:cbreak(), 34 | cecho:noecho(), 35 | cecho:curs_set(?ceCURS_INVISIBLE), 36 | %% Write initial string... 37 | cecho:mvaddstr(0, 0, "Hello World!"), 38 | cecho:refresh(), 39 | %% Start the process that will "move" the string 40 | Mover = spawn(fun() -> mvhello() end), 41 | ctrl(Mover). 42 | 43 | ctrl(Mover) -> 44 | %% get key-input 45 | C = cecho:getch(), 46 | case C of 47 | $q -> 48 | %% If we get a 'q' then exit the mover and stop cecho 49 | exit(Mover, normal), 50 | application:stop(cecho), 51 | erlang:halt(); 52 | _ -> 53 | %% ignore anything else 54 | ctrl(Mover) 55 | end. 56 | 57 | %% start the mover 58 | mvhello() -> mvhello(0, 0, 1, 1). 59 | 60 | %% take previous pos and direction and print out new string 61 | mvhello(PrevY, PrevX, DirY, DirX) -> 62 | %% "erase" previous position 63 | cecho:mvaddstr(PrevY, PrevX, " "), 64 | %% calculate new position and direction 65 | {NewY, NewX, NewDirY, NewDirX} = 66 | calc_new_pos(PrevY, PrevX, DirY, DirX), 67 | %% "move" the text to new position 68 | cecho:mvaddstr(NewY, NewX, "Hello World!"), 69 | %% update the screen to show the change 70 | cecho:refresh(), 71 | %% do it again! 72 | timer:sleep(50), 73 | mvhello(NewY, NewX, NewDirY, NewDirX). 74 | 75 | calc_new_pos(Py, Px, Dy, Dx) -> 76 | %% get max coords of the screen 77 | {My, Mx} = cecho:getmaxyx(), 78 | %% calc new vertical position and new direction 79 | {NewPy, NewDy} = 80 | if (Py+(Dy) >= My) orelse (Py+(Dy) < 0) -> 81 | {Py+(Dy*-1), Dy*-1}; 82 | true -> 83 | {Py+(Dy), Dy} 84 | end, 85 | %% calc new horizontal position and new direction 86 | %% take string length into account 87 | {NewPx, NewDx} = 88 | if (Px+(Dx)+12 >= Mx) orelse (Px+(Dx) < 0) -> 89 | {Px+(Dx*-1), Dx*-1}; 90 | true -> 91 | {Px+(Dx), Dx} 92 | end, 93 | {NewPy, NewPx, NewDy, NewDx}. 94 | 95 | There are several ways to run the examples and own scripts/app with cecho. There are essentially two _intended_ ways which are recommend; either running the whole thing as a script or from a module using the '-eval' flag to erl. 96 | 97 | Examples on how to run the helloworld example (make sure you have compiled cecho first): 98 | 99 | From the cecho directory do: 100 | 101 | $> erl -noinput -pa ../cecho/_build/default/lib/cecho/ebin/ -eval 'cecho_example:helloworld()' +A 50 102 | 103 | Or create an escript file as follows: 104 | 105 | #!/usr/bin/env escript 106 | %%! -noinput -pa ../cecho/_build/default/lib/cecho/ebin +A 50 107 | -include_lib("cecho/include/cecho.hrl"). 108 | main(_) -> cecho_example:helloworld(). 109 | 110 | and save it, then make it executable and run it from the cecho directory: 111 | 112 | $> chmod +x ./helloworld.escript 113 | $> ./helloworld.escript 114 | 115 | IMPORTANT: 116 | If input will be used (cecho:getch/0) then the two flags '-noinput' and +A _must_ be used. The reason is that if '-noinput' is not specified the erlang VM will interfere with the reading of the keys. The second reason is that in order to asynchronously read the input Erlang need more io threads; however it doesn't seem to be enough to specify e.g. 10 in some cases and the reason is not known. If a "high enough number" is specified then the input will work very well. 117 | 118 | ## Contribute 119 | Should you find yourself using cecho and have issues, comments or feedback please [create an issue!] [1] 120 | 121 | [1]: http://github.com/mazenharake/cecho/issues "cecho issues" 122 | -------------------------------------------------------------------------------- /c_src/cecho.c: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // Copyright (c) 2017, Mazen Harake 3 | // All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | //============================================================================== 26 | 27 | // Includes 28 | #include 29 | #include 30 | #include "cecho.h" 31 | #include "cecho_commands.h" 32 | #include "erl_driver.h" 33 | #if defined __has_include // Check if __has_include is present 34 | # if __has_include ("erl_interface.h") 35 | # include "erl_interface.h" 36 | # endif 37 | #elif defined(INCLUDE_ERL_INTERFACE) // Try with rebar3 defined attribute 38 | # include "erl_interface.h" 39 | #endif 40 | #include "ei.h" 41 | #include "ncurses.h" 42 | #include "assert.h" 43 | 44 | #if ERL_DRV_EXTENDED_MAJOR_VERSION < 2 45 | #define ErlDrvSizeT int 46 | #define ErlDrvSSizeT int 47 | #endif 48 | 49 | // State structure 50 | typedef struct { 51 | WINDOW *win[_MAXWINDOWS+1]; 52 | ei_x_buff eixb; 53 | char *args; 54 | int argslen; 55 | int index; 56 | int version; 57 | ErlDrvPort drv_port; 58 | char is_stdin_attached; 59 | } state; 60 | 61 | void init_state(state *st, char *args, int argslen); 62 | void ok(state *st); 63 | void error_tuple(state *st, int code); 64 | void boolean(state *st, int code); 65 | 66 | void tuple(ei_x_buff *eixb, int size); 67 | void atom(ei_x_buff *eixb, const char *str, int size); 68 | void integer(ei_x_buff *eixb, int integer); 69 | void string(ei_x_buff *eixb, const char *str); 70 | void encode_ok_reply(state *st, int code); 71 | int findfreewindowslot(state *st); 72 | void loop_getch(void *arg); 73 | 74 | void do_endwin(state *st); 75 | void do_initscr(state *st); 76 | void do_refresh(state *st); 77 | void do_cbreak(state *st); 78 | void do_nocbreak(state *st); 79 | void do_echo(state *st); 80 | void do_noecho(state *st); 81 | void do_addch(state *st); 82 | void do_addstr(state *st); 83 | void do_move(state *st); 84 | void do_getyx(state *st); 85 | void do_getmaxyx(state *st); 86 | void do_curs_set(state *st); 87 | void do_werase(state *st); 88 | void do_has_colors(state *st); 89 | void do_start_color(state *st); 90 | void do_init_pair(state *st); 91 | void do_curs_set(state *st); 92 | void do_init_pair(state *st); 93 | void do_wattron(state *st); 94 | void do_wattroff(state *st); 95 | void do_nl(state *st); 96 | void do_nonl(state *st); 97 | void do_scrollok(state *st); 98 | void do_mvaddch(state *st); 99 | void do_mvaddstr(state *st); 100 | void do_newwin(state *st); 101 | void do_delwin(state *st); 102 | void do_wmove(state *st); 103 | void do_waddstr(state *st); 104 | void do_waddch(state *st); 105 | void do_mvwaddstr(state *st); 106 | void do_mvwaddch(state *st); 107 | void do_wrefresh(state *st); 108 | void do_whline(state *st); 109 | void do_wvline(state *st); 110 | void do_wborder(state *st); 111 | void do_box(state *st); 112 | void do_keypad(state *st); 113 | void do_touchwin(state *st); 114 | 115 | // ============================================================================= 116 | // Erlang Callbacks 117 | // ============================================================================= 118 | static ErlDrvData start(ErlDrvPort port, char *command) { 119 | state *drvstate = (state *)driver_alloc(sizeof(state)); 120 | drvstate->is_stdin_attached = 0; 121 | drvstate->drv_port = port; 122 | set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); 123 | int i; 124 | for (i = 0; i < _MAXWINDOWS; i++) 125 | drvstate->win[i] = NULL; 126 | return (ErlDrvData)drvstate; 127 | } 128 | 129 | static void detach_stdin(state *st) { 130 | if (st->is_stdin_attached) { 131 | driver_select(st->drv_port, (ErlDrvEvent)(size_t)fileno(stdin), ERL_DRV_READ, 0); 132 | st->is_stdin_attached = 0; 133 | } 134 | } 135 | 136 | static void stop(ErlDrvData drvstate) { 137 | detach_stdin((state *)drvstate); 138 | driver_free(drvstate); 139 | } 140 | 141 | static void require_stdin(state *st) { 142 | if (!st->is_stdin_attached) { 143 | driver_select(st->drv_port, (ErlDrvEvent)(size_t)fileno(stdin), ERL_DRV_READ, 1); 144 | st->is_stdin_attached = 1; 145 | } 146 | } 147 | 148 | static void do_getch(ErlDrvData drvstate, ErlDrvEvent event) { 149 | state *st = (state *)drvstate; 150 | ei_x_buff eixb; 151 | int keycode; 152 | require_stdin(st); 153 | ei_x_new_with_version(&eixb); 154 | keycode = getch(); 155 | integer(&eixb, keycode); 156 | driver_output(st->drv_port, eixb.buff, eixb.index); 157 | } 158 | 159 | static ErlDrvSSizeT control(ErlDrvData drvstate, unsigned int command, 160 | char *args, ErlDrvSizeT argslen, 161 | char **rbuf, ErlDrvSizeT rbuflen) { 162 | state *st = (state *)drvstate; 163 | init_state(st, args, argslen); 164 | 165 | switch (command) { 166 | case ENDWIN: do_endwin(st); break; 167 | case INITSCR: do_initscr(st); break; 168 | case REFRESH: do_refresh(st); break; 169 | case CBREAK: do_cbreak(st); break; 170 | case NOCBREAK: do_nocbreak(st); break; 171 | case ECHO: do_echo(st); break; 172 | case NOECHO: do_noecho(st); break; 173 | case ADDCH: do_addch(st); break; 174 | case ADDSTR: do_addstr(st); break; 175 | case MOVE: do_move(st); break; 176 | case GETYX: do_getyx(st); break; 177 | case GETMAXYX: do_getmaxyx(st); break; 178 | case CURS_SET: do_curs_set(st); break; 179 | case WERASE: do_werase(st); break; 180 | case HAS_COLORS: do_has_colors(st); break; 181 | case START_COLOR: do_start_color(st); break; 182 | case INIT_PAIR: do_init_pair(st); break; 183 | case WATTRON: do_wattron(st); break; 184 | case WATTROFF: do_wattroff(st); break; 185 | case NL: do_nl(st); break; 186 | case NONL: do_nonl(st); break; 187 | case SCROLLOK: do_scrollok(st); break; 188 | case MVADDCH: do_mvaddch(st); break; 189 | case MVADDSTR: do_mvaddstr(st); break; 190 | case NEWWIN: do_newwin(st); break; 191 | case DELWIN: do_delwin(st); break; 192 | case WMOVE: do_wmove(st); break; 193 | case WADDSTR: do_waddstr(st); break; 194 | case WADDCH: do_waddch(st); break; 195 | case MVWADDSTR: do_mvwaddstr(st); break; 196 | case MVWADDCH: do_mvwaddch(st); break; 197 | case WREFRESH: do_wrefresh(st); break; 198 | case WHLINE: do_whline(st); break; 199 | case WVLINE: do_wvline(st); break; 200 | case WBORDER: do_wborder(st); break; 201 | case BOX: do_box(st); break; 202 | case KEYPAD: do_keypad(st); break; 203 | case TOUCHWIN: do_touchwin(st); break; 204 | default: break; 205 | } 206 | 207 | int rlen = st->eixb.index; 208 | ErlDrvBinary *response = driver_alloc_binary(rlen); 209 | memcpy(response->orig_bytes, st->eixb.buff, rlen); 210 | ei_x_free(&(st->eixb)); 211 | *rbuf = (char *)response; 212 | return rlen; 213 | } 214 | 215 | // ============================================================================= 216 | // NCurses function wrappers 217 | // =========================================================================== 218 | void do_endwin(state *st) { 219 | detach_stdin(st); 220 | encode_ok_reply(st, endwin()); 221 | } 222 | 223 | void do_initscr(state *st) { 224 | st->win[0] = (WINDOW *)initscr(); 225 | require_stdin(st); 226 | if (st->win[0] == NULL) { 227 | encode_ok_reply(st, -1); 228 | } else { 229 | encode_ok_reply(st, 0); 230 | } 231 | } 232 | 233 | void do_refresh(state *st) { 234 | require_stdin(st); 235 | encode_ok_reply(st, refresh()); 236 | } 237 | 238 | void do_cbreak(state *st) { 239 | encode_ok_reply(st, cbreak()); 240 | } 241 | void do_nocbreak(state *st) { 242 | encode_ok_reply(st, nocbreak()); 243 | } 244 | 245 | void do_echo(state *st) { 246 | encode_ok_reply(st, echo()); 247 | } 248 | 249 | void do_noecho(state *st) { 250 | encode_ok_reply(st, noecho()); 251 | } 252 | 253 | void do_addch(state *st) { 254 | long ch; 255 | ei_decode_long(st->args, &(st->index), &ch); 256 | encode_ok_reply(st, addch((chtype)ch)); 257 | } 258 | 259 | void do_addstr(state *st) { 260 | int arity; 261 | long strlen; 262 | char *str = NULL; 263 | int code = 0; 264 | ei_decode_tuple_header(st->args, &(st->index), &arity); 265 | ei_decode_long(st->args, &(st->index), &strlen); 266 | if ( (str = calloc(strlen+1, 1)) == NULL) { 267 | encode_ok_reply(st, ENOMEM); 268 | return; 269 | } 270 | ei_decode_string(st->args, &(st->index), str); 271 | code = addnstr(str, strlen); 272 | free(str); 273 | encode_ok_reply(st, code); 274 | } 275 | 276 | void do_move(state *st) { 277 | int arity; 278 | long y, x; 279 | ei_decode_tuple_header(st->args, &(st->index), &arity); 280 | ei_decode_long(st->args, &(st->index), &y); 281 | ei_decode_long(st->args, &(st->index), &x); 282 | encode_ok_reply(st, move((int)y, (int)x)); 283 | } 284 | 285 | void do_getyx(state *st) { 286 | long slot; 287 | int x, y; 288 | ei_decode_long(st->args, &(st->index), &slot); 289 | getyx(st->win[slot], y, x); 290 | tuple(&(st->eixb), 2); 291 | integer(&(st->eixb), y); 292 | integer(&(st->eixb), x); 293 | } 294 | 295 | void do_getmaxyx(state *st) { 296 | long slot; 297 | int x, y; 298 | ei_decode_long(st->args, &(st->index), &slot); 299 | getmaxyx(st->win[slot], y, x); 300 | tuple(&(st->eixb), 2); 301 | integer(&(st->eixb), y); 302 | integer(&(st->eixb), x); 303 | } 304 | 305 | void do_curs_set(state *st) { 306 | long flag; 307 | ei_decode_long(st->args, &(st->index), &flag); 308 | curs_set((int)flag); 309 | ok(st); 310 | } 311 | 312 | void do_werase(state *st) { 313 | long slot; 314 | ei_decode_long(st->args, &(st->index), &slot); 315 | encode_ok_reply(st, werase(st->win[slot])); 316 | } 317 | 318 | void do_has_colors(state *st) { 319 | boolean(st, has_colors()); 320 | } 321 | 322 | void do_start_color(state *st) { 323 | use_default_colors(); 324 | encode_ok_reply(st, start_color()); 325 | } 326 | 327 | void do_init_pair(state *st) { 328 | int arity; 329 | long pairnum, fcolor, bcolor; 330 | ei_decode_tuple_header(st->args, &(st->index), &arity); 331 | ei_decode_long(st->args, &(st->index), &pairnum); 332 | ei_decode_long(st->args, &(st->index), &fcolor); 333 | ei_decode_long(st->args, &(st->index), &bcolor); 334 | encode_ok_reply(st, init_pair((int)pairnum, (int)fcolor, (int)bcolor)); 335 | } 336 | 337 | void do_wattron(state *st) { 338 | int arity; 339 | long slot, attrs; 340 | ei_decode_tuple_header(st->args, &(st->index), &arity); 341 | ei_decode_long(st->args, &(st->index), &slot); 342 | ei_decode_long(st->args, &(st->index), &attrs); 343 | encode_ok_reply(st, wattron(st->win[slot], (int)attrs)); 344 | } 345 | 346 | void do_wattroff(state *st) { 347 | int arity; 348 | long slot, attrs; 349 | ei_decode_tuple_header(st->args, &(st->index), &arity); 350 | ei_decode_long(st->args, &(st->index), &slot); 351 | ei_decode_long(st->args, &(st->index), &attrs); 352 | encode_ok_reply(st, wattroff(st->win[slot], (int)attrs)); 353 | } 354 | 355 | void do_nl(state *st) { 356 | encode_ok_reply(st, nl()); 357 | } 358 | 359 | void do_nonl(state *st) { 360 | encode_ok_reply(st, nonl()); 361 | } 362 | 363 | void do_scrollok(state *st) { 364 | int arity; 365 | int bf; 366 | long slot; 367 | ei_decode_tuple_header(st->args, &(st->index), &arity); 368 | ei_decode_long(st->args, &(st->index), &slot); 369 | ei_decode_boolean(st->args, &(st->index), &bf); 370 | encode_ok_reply(st, scrollok(st->win[slot], bf)); 371 | } 372 | 373 | void do_mvaddch(state *st) { 374 | int arity; 375 | long y, x, ch; 376 | ei_decode_tuple_header(st->args, &(st->index), &arity); 377 | ei_decode_long(st->args, &(st->index), &y); 378 | ei_decode_long(st->args, &(st->index), &x); 379 | ei_decode_long(st->args, &(st->index), &ch); 380 | encode_ok_reply(st, mvaddch((int)y, (int)x, (chtype)ch)); 381 | } 382 | 383 | void do_mvaddstr(state *st) { 384 | int arity; 385 | long strlen, y, x; 386 | char *str = NULL; 387 | int code = 0; 388 | ei_decode_tuple_header(st->args, &(st->index), &arity); 389 | ei_decode_long(st->args, &(st->index), &y); 390 | ei_decode_long(st->args, &(st->index), &x); 391 | ei_decode_long(st->args, &(st->index), &strlen); 392 | if ( (str = calloc(strlen+1, 1)) == NULL) { 393 | encode_ok_reply(st, ENOMEM); 394 | return; 395 | } 396 | ei_decode_string(st->args, &(st->index), str); 397 | code = mvaddnstr((int)y, (int)x, str, (int)strlen); 398 | free(str); 399 | encode_ok_reply(st, code); 400 | } 401 | 402 | void do_newwin(state *st) { 403 | int slot = findfreewindowslot(st); 404 | if (slot > 0) { 405 | int arity; 406 | long height, width, starty, startx; 407 | ei_decode_tuple_header(st->args, &(st->index), &arity); 408 | ei_decode_long(st->args, &(st->index), &height); 409 | ei_decode_long(st->args, &(st->index), &width); 410 | ei_decode_long(st->args, &(st->index), &starty); 411 | ei_decode_long(st->args, &(st->index), &startx); 412 | st->win[slot] = newwin(height, width, starty, startx); 413 | integer(&(st->eixb), slot); 414 | } else { 415 | integer(&(st->eixb), -1); 416 | } 417 | } 418 | 419 | void do_delwin(state *st) { 420 | long slot; 421 | ei_decode_long(st->args, &(st->index), &slot); 422 | if (slot == 0) { 423 | boolean(st, FALSE); 424 | } else if (st->win[slot] == NULL) { 425 | boolean(st, FALSE); 426 | } else if (st->win[slot] != NULL) { 427 | delwin(st->win[slot]); 428 | st->win[slot] = NULL; 429 | boolean(st, TRUE); 430 | } 431 | } 432 | 433 | void do_wmove(state *st) { 434 | int arity; 435 | long slot, y, x; 436 | ei_decode_tuple_header(st->args, &(st->index), &arity); 437 | ei_decode_long(st->args, &(st->index), &slot); 438 | ei_decode_long(st->args, &(st->index), &y); 439 | ei_decode_long(st->args, &(st->index), &x); 440 | encode_ok_reply(st, wmove(st->win[slot], (int)y, (int)x)); 441 | } 442 | 443 | void do_waddstr(state *st) { 444 | int arity; 445 | long slot, strlen; 446 | char *str = NULL; 447 | int code = 0; 448 | ei_decode_tuple_header(st->args, &(st->index), &arity); 449 | ei_decode_long(st->args, &(st->index), &slot); 450 | ei_decode_long(st->args, &(st->index), &strlen); 451 | if ( (str = calloc(strlen+1, 1)) == NULL) { 452 | encode_ok_reply(st, ENOMEM); 453 | return; 454 | } 455 | ei_decode_string(st->args, &(st->index), str); 456 | code = waddnstr(st->win[slot], str, strlen); 457 | free(str); 458 | encode_ok_reply(st, code); 459 | } 460 | 461 | void do_waddch(state *st) { 462 | int arity; 463 | long slot; 464 | char ch = 0; 465 | ei_decode_tuple_header(st->args, &(st->index), &arity); 466 | ei_decode_long(st->args, &(st->index), &slot); 467 | ei_decode_char(st->args, &(st->index), &ch); 468 | encode_ok_reply(st, waddch(st->win[slot], ch)); 469 | } 470 | 471 | void do_mvwaddstr(state *st) { 472 | int arity; 473 | long slot, y, x, strlen; 474 | char *str = NULL; 475 | int code = 0; 476 | ei_decode_tuple_header(st->args, &(st->index), &arity); 477 | ei_decode_long(st->args, &(st->index), &slot); 478 | ei_decode_long(st->args, &(st->index), &y); 479 | ei_decode_long(st->args, &(st->index), &x); 480 | ei_decode_long(st->args, &(st->index), &strlen); 481 | if ( (str = calloc(strlen+1, 1)) == NULL) { 482 | encode_ok_reply(st, ENOMEM); 483 | return; 484 | } 485 | ei_decode_string(st->args, &(st->index), str); 486 | code = mvwaddnstr(st->win[slot], (int)y, (int)x, str, strlen); 487 | free(str); 488 | encode_ok_reply(st, code); 489 | } 490 | 491 | void do_mvwaddch(state *st) { 492 | int arity; 493 | long slot, y, x; 494 | char ch = 0; 495 | ei_decode_tuple_header(st->args, &(st->index), &arity); 496 | ei_decode_long(st->args, &(st->index), &slot); 497 | ei_decode_long(st->args, &(st->index), &y); 498 | ei_decode_long(st->args, &(st->index), &x); 499 | ei_decode_char(st->args, &(st->index), &ch); 500 | encode_ok_reply(st, mvwaddch(st->win[slot], (int)y, (int)x, ch)); 501 | } 502 | 503 | void do_wrefresh(state *st) { 504 | long slot; 505 | ei_decode_long(st->args, &(st->index), &slot); 506 | encode_ok_reply(st, wrefresh(st->win[slot])); 507 | } 508 | 509 | void do_whline(state *st) { 510 | int arity; 511 | long slot, ch, max; 512 | ei_decode_tuple_header(st->args, &(st->index), &arity); 513 | ei_decode_long(st->args, &(st->index), &slot); 514 | ei_decode_long(st->args, &(st->index), &ch); 515 | ei_decode_long(st->args, &(st->index), &max); 516 | encode_ok_reply(st, whline(st->win[slot], (chtype)ch, (int)max)); 517 | } 518 | 519 | void do_wvline(state *st) { 520 | int arity; 521 | long slot, ch, max; 522 | ei_decode_tuple_header(st->args, &(st->index), &arity); 523 | ei_decode_long(st->args, &(st->index), &slot); 524 | ei_decode_long(st->args, &(st->index), &ch); 525 | ei_decode_long(st->args, &(st->index), &max); 526 | encode_ok_reply(st, wvline(st->win[slot], (chtype)ch, (int)max)); 527 | } 528 | 529 | void do_wborder(state *st) { 530 | int arity; 531 | long slot, ls, rs, ts, bs, tl, tr, bl, br; 532 | ei_decode_tuple_header(st->args, &(st->index), &arity); 533 | ei_decode_long(st->args, &(st->index), &slot); 534 | ei_decode_long(st->args, &(st->index), &ls); 535 | ei_decode_long(st->args, &(st->index), &rs); 536 | ei_decode_long(st->args, &(st->index), &ts); 537 | ei_decode_long(st->args, &(st->index), &bs); 538 | ei_decode_long(st->args, &(st->index), &tl); 539 | ei_decode_long(st->args, &(st->index), &tr); 540 | ei_decode_long(st->args, &(st->index), &bl); 541 | ei_decode_long(st->args, &(st->index), &br); 542 | encode_ok_reply(st, wborder(st->win[slot], (chtype)ls, (chtype)rs, (chtype)ts, 543 | (chtype)bs, (chtype)tl, (chtype)tr, (chtype)bl, 544 | (chtype)br)); 545 | } 546 | 547 | void do_box(state *st) { 548 | int arity; 549 | long slot, verch, horch; 550 | ei_decode_tuple_header(st->args, &(st->index), &arity); 551 | ei_decode_long(st->args, &(st->index), &slot); 552 | ei_decode_long(st->args, &(st->index), &verch); 553 | ei_decode_long(st->args, &(st->index), &horch); 554 | encode_ok_reply(st, box(st->win[slot], (chtype)verch, (chtype)horch)); 555 | } 556 | 557 | void do_keypad(state *st) { 558 | int arity, bf; 559 | long slot; 560 | ei_decode_tuple_header(st->args, &(st->index), &arity); 561 | ei_decode_long(st->args, &(st->index), &slot); 562 | ei_decode_boolean(st->args, &(st->index), &bf); 563 | encode_ok_reply(st, keypad(st->win[slot], bf)); 564 | } 565 | 566 | void do_touchwin(state *st) { 567 | long slot; 568 | ei_decode_long(st->args, &(st->index), &slot); 569 | if (st->win[slot] != NULL) { 570 | touchwin(st->win[slot]); 571 | boolean(st, TRUE); 572 | } 573 | } 574 | 575 | // ============================================================================= 576 | // Utility functions 577 | // ============================================================================= 578 | void init_state(state *st, char *args, int argslen) { 579 | st->index = 0; 580 | st->version = 0; 581 | st->args = args; 582 | st->argslen = argslen; 583 | ei_decode_version(st->args, &(st->index), &(st->version)); 584 | assert(st->version != 0); 585 | assert(st->index != 0); 586 | ei_x_new_with_version(&(st->eixb)); 587 | } 588 | 589 | void ok(state *st) { 590 | atom(&(st->eixb), "ok", 2); 591 | } 592 | 593 | void error_tuple(state *st, int code) { 594 | tuple(&(st->eixb), 2); 595 | atom(&(st->eixb), "error", 5); 596 | integer(&(st->eixb), code); 597 | } 598 | 599 | void boolean(state *st, int code) { 600 | if (code == TRUE) 601 | atom(&(st->eixb),"true",4); 602 | else 603 | atom(&(st->eixb),"false",5); 604 | } 605 | 606 | void tuple(ei_x_buff *eixb, int size) { 607 | ei_x_encode_tuple_header(eixb, size); 608 | } 609 | 610 | void atom(ei_x_buff *eixb, const char *str, int size) { 611 | ei_x_encode_atom_len(eixb, str, size); 612 | } 613 | 614 | void integer(ei_x_buff *eixb, int integer) { 615 | ei_x_encode_long(eixb, (long)integer); 616 | } 617 | 618 | void string(ei_x_buff *eixb, const char *str) { 619 | ei_x_encode_string(eixb, str); 620 | } 621 | 622 | void encode_ok_reply(state *st, int code) { 623 | if (code == OK) { 624 | ok(st); 625 | } else { 626 | error_tuple(st, code); 627 | } 628 | } 629 | 630 | int findfreewindowslot(state *st) { 631 | int i; 632 | for (i = 0; i < _MAXWINDOWS; i++) 633 | if (st->win[i] == NULL) return i; 634 | return -1; 635 | } 636 | 637 | // ============================================================================= 638 | // Erlang driver_entry Specification 639 | // =========================================================================== 640 | ErlDrvEntry driver_entry = { 641 | NULL, 642 | start, 643 | stop, 644 | NULL, 645 | do_getch, 646 | NULL, 647 | "cecho", 648 | NULL, 649 | NULL, 650 | control, 651 | NULL, 652 | NULL, 653 | NULL, 654 | NULL, 655 | NULL, 656 | NULL, 657 | ERL_DRV_EXTENDED_MARKER, 658 | ERL_DRV_EXTENDED_MAJOR_VERSION, 659 | ERL_DRV_EXTENDED_MINOR_VERSION 660 | }; 661 | 662 | // ============================================================================= 663 | // Erlang Driver Name 664 | // ============================================================================= 665 | DRIVER_INIT(cecho) { 666 | return &driver_entry; 667 | } 668 | -------------------------------------------------------------------------------- /c_src/cecho.h: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // Copyright (c) 2017, Mazen Harake 3 | // All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | //============================================================================== 26 | 27 | #define _INT(A1,A2,A3,A4) ((int)(A1) << 24 | (A2) << 16 | (A3) << 8 | (A4)) 28 | 29 | #define _MAXWINDOWS 60 30 | -------------------------------------------------------------------------------- /c_src/cecho_commands.h: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // Copyright (c) 2017, Mazen Harake 3 | // All rights reserved. 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are met: 7 | // 8 | // * Redistributions of source code must retain the above copyright notice, 9 | // this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above copyright 11 | // notice, this list of conditions and the following disclaimer in the 12 | // documentation and/or other materials provided with the distribution. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | //============================================================================== 26 | 27 | #define ENDWIN 0 28 | #define INITSCR 1 29 | #define REFRESH 2 30 | #define CBREAK 3 31 | #define NOCBREAK 4 32 | #define ECHO 5 33 | #define NOECHO 6 34 | #define ADDCH 7 35 | #define ADDSTR 8 36 | #define MOVE 9 37 | #define GETYX 10 38 | #define GETMAXYX 11 39 | #define CURS_SET 12 40 | #define WERASE 13 41 | #define HAS_COLORS 14 42 | #define START_COLOR 15 43 | #define INIT_PAIR 16 44 | #define WATTRON 17 45 | #define WATTROFF 18 46 | #define NL 19 47 | #define NONL 20 48 | #define SCROLLOK 21 49 | #define MVADDCH 22 50 | #define MVADDSTR 23 51 | #define NEWWIN 24 52 | #define DELWIN 25 53 | #define WMOVE 26 54 | #define WADDSTR 27 55 | #define WADDCH 28 56 | #define MVWADDSTR 29 57 | #define MVWADDCH 30 58 | #define WREFRESH 31 59 | #define WHLINE 32 60 | #define WVLINE 33 61 | #define WBORDER 34 62 | #define BOX 35 63 | #define KEYPAD 36 64 | #define TOUCHWIN 37 65 | -------------------------------------------------------------------------------- /examples.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %%! -noinput -pa ../cecho/_build/default/lib/cecho/ebin +A 50 3 | 4 | %% Read the LICENSE file 5 | -include_lib("cecho/include/cecho.hrl"). 6 | main(["countdown"]) -> 7 | cecho_example:countdown(); 8 | main(["simple"]) -> 9 | cecho_example:simple(); 10 | main(["colors"]) -> 11 | cecho_example:colors(); 12 | main(["input"]) -> 13 | cecho_example:input(); 14 | main(["cursmove"]) -> 15 | cecho_example:cursmove(); 16 | main(["helloworld"]) -> 17 | cecho_example:helloworld(); 18 | main(_) -> 19 | io:format("Usage: ./examples.escript \n"), 20 | io:format("\n"), 21 | io:format("where is one of\n"), 22 | io:format("\n"), 23 | io:format(" countdown - Simple countdown which shows\n"), 24 | io:format(" how to print, move and get\n"), 25 | io:format(" coordinates\n"), 26 | io:format("\n"), 27 | io:format(" simple - Simple example to show usage\n"), 28 | io:format("\n"), 29 | io:format(" colors - Fun with colors\n"), 30 | io:format("\n"), 31 | io:format(" input - Prints a number continuously as another\n"), 32 | io:format(" io thread is waiting for keyinput\n"), 33 | io:format("\n"), 34 | io:format(" cursmove - move the '@' around the screen with the\n"), 35 | io:format(" arrow keys. 'q' to quit.\n"), 36 | io:format("\n"), 37 | io:format(" helloworld - bounce 'Hello World!' around the screen\n"). 38 | -------------------------------------------------------------------------------- /helloworld.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %%! -noinput -pa ../cecho/_build/default/lib/cecho/ebin +A 50 3 | 4 | %% Read the LICENSE file 5 | -include_lib("cecho/include/cecho.hrl"). 6 | main(_) -> cecho_example:helloworld(). 7 | -------------------------------------------------------------------------------- /include/cecho.hrl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -define(ceTRUE, 1). 28 | -define(ceFALSE, 0). 29 | 30 | -define(ceCURS_INVISIBLE, 0). 31 | -define(ceCURS_NORMAL, 1). 32 | -define(ceCURS_VERY_VISIBLE, 2). 33 | 34 | -define(ceCOLOR_BLACK, 0). 35 | -define(ceCOLOR_RED, 1). 36 | -define(ceCOLOR_GREEN, 2). 37 | -define(ceCOLOR_YELLOW, 3). 38 | -define(ceCOLOR_BLUE, 4). 39 | -define(ceCOLOR_MAGENTA, 5). 40 | -define(ceCOLOR_CYAN, 6). 41 | -define(ceCOLOR_WHITE, 7). 42 | 43 | -define(ceA_NORMAL, 0). 44 | -define(ceCOLOR_PAIR(C), (C bsl 8)). 45 | -define(ceA_BOLD, (1 bsl (8 + 13))). 46 | -define(ceA_UNDERLINE, (1 bsl (8 + 9))). 47 | -define(ceA_REVERSE, (1 bsl (8 + 10))). 48 | -define(ceA_BLINK, (1 bsl (8 + 11))). 49 | 50 | -define(ceSTDSCR, 0). 51 | 52 | -define(ceACS_DIAMOND, 4194400). 53 | -define(ceACS_CKBOARD, 4194401). 54 | -define(ceACS_DEGREE, 4194406). 55 | -define(ceACS_PLMINUS, 4194407). 56 | -define(ceACS_BOARD, 4194408). 57 | -define(ceACS_LANTERN, 4194409). 58 | -define(ceACS_LRCORNER, 4194410). 59 | -define(ceACS_URCORNER, 4194411). 60 | -define(ceACS_ULCORNER, 4194412). 61 | -define(ceACS_LLCORNER, 4194413). 62 | -define(ceACS_PLUS, 4194414). 63 | -define(ceACS_S1, 4194415). 64 | -define(ceACS_S3, 4194416). 65 | -define(ceACS_HLINE, 4194417). 66 | -define(ceACS_S7, 4194418). 67 | -define(ceACS_S9, 4194419). 68 | -define(ceACS_LTEE, 4194420). 69 | -define(ceACS_RTEE, 4194421). 70 | -define(ceACS_BTEE, 4194422). 71 | -define(ceACS_TTEE, 4194423). 72 | -define(ceACS_VLINE, 4194424). 73 | -define(ceACS_LEQUAL, 4194425). 74 | -define(ceACS_GEQUAL, 4194426). 75 | -define(ceACS_PI, 4194427). 76 | -define(ceACS_NEQUAL, 4194428). 77 | -define(ceACS_STERLING, 4194429). 78 | -define(ceACS_BULLET, 4194430). 79 | -define(ceACS_RARROW, 4194347). 80 | -define(ceACS_LARROW, 4194348). 81 | -define(ceACS_UARROW, 4194349). 82 | -define(ceACS_DARROW, 4194350). 83 | -define(ceACS_BLOCK, 4194352). 84 | 85 | -define(ceKEY_TAB, 9). 86 | -define(ceKEY_ESC, 27). 87 | 88 | 89 | -define(ceKEY_DOWN, 258). 90 | -define(ceKEY_UP, 259). 91 | -define(ceKEY_LEFT, 260). 92 | -define(ceKEY_RIGHT, 261). 93 | -define(ceKEY_HOME, 262). 94 | -define(ceKEY_BACKSPACE, 263). 95 | -define(ceKEY_F(N), 264+N). 96 | -define(ceKEY_DEL, 330). 97 | -define(ceKEY_INS, 331). 98 | -define(ceKEY_PGDOWN, 338). 99 | -define(ceKEY_PGUP, 339). 100 | -define(ceKEY_END, 360). 101 | 102 | 103 | -------------------------------------------------------------------------------- /include/cecho_commands.hrl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -define(ENDWIN, 0). 28 | -define(INITSCR, 1). 29 | -define(REFRESH, 2). 30 | -define(CBREAK, 3). 31 | -define(NOCBREAK, 4). 32 | -define(ECHO, 5). 33 | -define(NOECHO, 6). 34 | -define(ADDCH, 7). 35 | -define(ADDSTR, 8). 36 | -define(MOVE, 9). 37 | -define(GETYX, 10). 38 | -define(GETMAXYX, 11). 39 | -define(CURS_SET, 12). 40 | -define(WERASE, 13). 41 | -define(HAS_COLORS, 14). 42 | -define(START_COLOR, 15). 43 | -define(INIT_PAIR, 16). 44 | -define(WATTRON, 17). 45 | -define(WATTROFF, 18). 46 | -define(NL, 19). 47 | -define(NONL, 20). 48 | -define(SCROLLOK, 21). 49 | -define(MVADDCH, 22). 50 | -define(MVADDSTR, 23). 51 | -define(NEWWIN, 24). 52 | -define(DELWIN, 25). 53 | -define(WMOVE, 26). 54 | -define(WADDSTR, 27). 55 | -define(WADDCH, 28). 56 | -define(MVWADDSTR, 29). 57 | -define(MVWADDCH, 30). 58 | -define(WREFRESH, 31). 59 | -define(WHLINE, 32). 60 | -define(WVLINE, 33). 61 | -define(WBORDER, 34). 62 | -define(BOX, 35). 63 | -define(KEYPAD, 36). 64 | -define(TOUCHWIN, 37). 65 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info, 2 | {platform_define, "^(R14|R15|R16B|17)", 'random_module_available'}, 3 | {if_version_below, "23", {d, 'INCLUDE_ERL_INTERFACE'}} 4 | ]}. 5 | 6 | {plugins, [ 7 | {pc, {git, "https://github.com/blt/port_compiler.git", {branch, "master"}}} 8 | ]}. 9 | {port_specs, [{"priv/cecho.so", ["c_src/cecho.c"]}]}. 10 | {port_env, [ 11 | {"DRV_LDFLAGS", "$DRV_LDFLAGS -lncurses"} 12 | ] 13 | }. 14 | {so_name, "cecho.so"}. 15 | {artifacts, ["priv/cecho.so"]}. 16 | {provider_hooks, [{pre, [{compile, {pc, compile}}, {clean, {pc, clean}}]}]}. 17 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mazenharake/cecho/dabe9f35def4085420eada4f629fbbc2b45a858b/rebar3 -------------------------------------------------------------------------------- /src/cecho.app.src: -------------------------------------------------------------------------------- 1 | {application, cecho, 2 | [{description, "An ncurses library for Erlang"}, 3 | {vsn, "0.5.2"}, 4 | {registered, []}, 5 | {mod, { cecho, []}}, 6 | {applications, 7 | [kernel, 8 | stdlib 9 | ]}, 10 | {env,[]}, 11 | {modules, [cecho, cecho_srv]}, 12 | 13 | {maintainers, []}, 14 | {licenses, ["Apache 2.0"]}, 15 | {links, []} 16 | ]}. 17 | -------------------------------------------------------------------------------- /src/cecho.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(cecho). 28 | 29 | -author('mazen.harake@gmail.com'). 30 | 31 | -behaviour(application). 32 | -include("include/cecho.hrl"). 33 | -include("include/cecho_commands.hrl"). 34 | 35 | %% Behaviour Callbacks 36 | -export([start/2, stop/1]). 37 | 38 | %% Application API 39 | 40 | -export([refresh/0, cbreak/0, nocbreak/0, echo/0, noecho/0, addch/1, addstr/1, 41 | move/2, getyx/0, getmaxyx/0, curs_set/1, werase/1, erase/0, has_colors/0, 42 | start_color/0, init_pair/3, attron/1, attroff/1, nl/0, nonl/0, 43 | scrollok/2, mvaddch/3, mvaddstr/3, newwin/4, delwin/1, wmove/3, 44 | waddstr/2, waddch/2, mvwaddstr/4, mvwaddch/4, wrefresh/1, hline/2, 45 | whline/3, vline/2, wvline/3, border/8, wborder/9, box/3, getyx/1, 46 | getmaxyx/1, attron/2, attroff/2, keypad/2, getch/0, touchwin/1, 47 | endwin/0]). 48 | 49 | %% ============================================================================= 50 | %% Application API 51 | %% ============================================================================= 52 | refresh() -> 53 | call(?REFRESH). 54 | 55 | cbreak() -> 56 | call(?CBREAK). 57 | 58 | nocbreak() -> 59 | call(?NOCBREAK). 60 | 61 | echo() -> 62 | call(?ECHO). 63 | 64 | noecho() -> 65 | call(?NOECHO). 66 | 67 | addch(Char) when is_integer(Char) -> 68 | call(?ADDCH, Char). 69 | 70 | addstr(String) when is_list(String) -> 71 | Str = lists:flatten(String), 72 | call(?ADDSTR, {erlang:iolist_size(Str), Str}). 73 | 74 | move(Y, X) when is_integer(X) andalso is_integer(Y) -> 75 | call(?MOVE, {Y, X}). 76 | 77 | getyx() -> 78 | getyx(?ceSTDSCR). 79 | 80 | getyx(Window) when is_integer(Window) -> 81 | call(?GETYX, Window). 82 | 83 | getmaxyx() -> 84 | getmaxyx(?ceSTDSCR). 85 | 86 | getmaxyx(Window) when is_integer(Window) -> 87 | call(?GETMAXYX, Window). 88 | 89 | curs_set(Flag) when is_integer(Flag) -> 90 | call(?CURS_SET, Flag). 91 | 92 | erase() -> 93 | werase(?ceSTDSCR). 94 | 95 | werase(Window) when is_integer(Window) -> 96 | call(?WERASE, Window). 97 | 98 | has_colors() -> 99 | call(?HAS_COLORS). 100 | 101 | start_color() -> 102 | call(?START_COLOR). 103 | 104 | init_pair(N, FColor, BColor) when is_integer(N) andalso is_integer(FColor) 105 | andalso is_integer(BColor) -> 106 | call(?INIT_PAIR, {N, FColor, BColor}). 107 | 108 | attron(Mask) -> 109 | attron(?ceSTDSCR, Mask). 110 | 111 | attron(Window, Mask) when is_integer(Mask) andalso is_integer(Window) -> 112 | call(?WATTRON, {Window, Mask}). 113 | 114 | attroff(Mask) -> 115 | attroff(?ceSTDSCR, Mask). 116 | 117 | attroff(Window, Mask) when is_integer(Mask) andalso is_integer(Window) -> 118 | call(?WATTROFF, {Window, Mask}). 119 | 120 | nl() -> 121 | call(?NL). 122 | 123 | nonl() -> 124 | call(?NONL). 125 | 126 | scrollok(Window, BFlag) when is_integer(Window) andalso is_boolean(BFlag) -> 127 | call(?SCROLLOK, {Window, BFlag}). 128 | 129 | mvaddch(Y, X, Char) when is_integer(Char) andalso is_integer(X) 130 | andalso is_integer(Y) -> 131 | call(?MVADDCH, {Y, X, Char}). 132 | 133 | mvaddstr(Y, X, String) when is_list(String) andalso is_integer(X) andalso 134 | is_integer(Y) -> 135 | Str = lists:flatten(String), 136 | call(?MVADDSTR, {Y, X, erlang:iolist_size(Str), Str}). 137 | 138 | newwin(Height, Width, StartY, StartX) when is_integer(Height) andalso 139 | is_integer(Width) andalso 140 | is_integer(StartY) andalso 141 | is_integer(StartX) -> 142 | call(?NEWWIN, {Height, Width, StartY, StartX}). 143 | 144 | delwin(Window) when is_integer(Window) -> 145 | call(?DELWIN, Window). 146 | 147 | wmove(Window, Y, X) when is_integer(Window) andalso is_integer(Y) andalso 148 | is_integer(X) -> 149 | call(?WMOVE, {Window, Y, X}). 150 | 151 | waddstr(Window, String) when is_integer(Window) andalso is_list(String) -> 152 | Str = lists:flatten(String), 153 | call(?WADDSTR, {Window, erlang:iolist_size(Str), Str}). 154 | 155 | waddch(Window, Char) when is_integer(Window) andalso is_integer(Char) -> 156 | call(?WADDCH, {Window, Char}). 157 | 158 | mvwaddstr(Window, Y, X, String) when is_integer(Window) andalso is_integer(Y) 159 | andalso is_integer(X) andalso 160 | is_list(String) -> 161 | Str = lists:flatten(String), 162 | call(?MVWADDSTR, {Window, Y, X, erlang:iolist_size(Str), Str}). 163 | 164 | mvwaddch(Window, Y, X, Char) when is_integer(Window) andalso is_integer(Y) 165 | andalso is_integer(X) -> 166 | call(?MVWADDCH, {Window, Y, X, Char}). 167 | 168 | wrefresh(Window) when is_integer(Window) -> 169 | call(?WREFRESH, Window). 170 | 171 | hline(Char, MaxN) -> 172 | whline(?ceSTDSCR, Char, MaxN). 173 | 174 | whline(Window, Char, MaxN) when is_integer(Window) andalso is_integer(MaxN) -> 175 | call(?WHLINE, {Window, Char, MaxN}). 176 | 177 | vline(Char, MaxN) -> 178 | wvline(?ceSTDSCR, Char, MaxN). 179 | 180 | wvline(Window, Char, MaxN) when is_integer(Window) andalso is_integer(MaxN) -> 181 | call(?WVLINE, {Window, Char, MaxN}). 182 | 183 | border(Ls, Rs, Ts, Bs, TLs, TRs, BLs, BRs) -> 184 | wborder(0, Ls, Rs, Ts, Bs, TLs, TRs, BLs, BRs). 185 | 186 | wborder(Window, Ls, Rs, Ts, Bs, TLs, TRs, BLs, BRs) 187 | when is_integer(Ls) andalso is_integer(Rs) andalso 188 | is_integer(Ts) andalso is_integer(Bs) andalso 189 | is_integer(TLs) andalso is_integer(TRs) andalso 190 | is_integer(BLs) andalso is_integer(BRs) -> 191 | call(?WBORDER, {Window, Ls, Rs, Ts, Bs, TLs, TRs, BLs, BRs}). 192 | 193 | box(Window, Vert, Horz) when is_integer(Window) andalso is_integer(Vert) andalso 194 | is_integer(Horz) -> 195 | call(?BOX, {Window, Vert, Horz}). 196 | 197 | keypad(Window, BFlag) when is_integer(Window) andalso is_boolean(BFlag) -> 198 | call(?KEYPAD, {Window, BFlag}). 199 | 200 | touchwin(Window) when is_integer(Window) -> 201 | call(?TOUCHWIN, Window). 202 | 203 | getch() -> 204 | cecho_srv:getch(). 205 | 206 | endwin() -> 207 | call(?ENDWIN). 208 | 209 | %% ============================================================================= 210 | %% Behaviour Callbacks 211 | %% ============================================================================= 212 | start(_, _) -> 213 | cecho_srv:start_link(). 214 | 215 | stop(_) -> 216 | ok. 217 | 218 | %% ============================================================================= 219 | %% Internal Functions 220 | %% ============================================================================= 221 | call(Cmd) -> 222 | call(Cmd, undefined). 223 | 224 | call(Cmd, Args) -> 225 | cecho_srv:call(Cmd, Args). 226 | -------------------------------------------------------------------------------- /src/cecho_example.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(cecho_example). 28 | 29 | -author('mazen.harake@gmail.com'). 30 | 31 | -include("cecho.hrl"). 32 | 33 | -export ([countdown/0, 34 | simple/0, 35 | colors/0, 36 | pos/2, 37 | input/0, input_counter/1, 38 | cursmove/0, 39 | helloworld/0 40 | ]). 41 | 42 | %% 43 | %% Simple countdown which shows how to print, move and get coordinates 44 | %% 45 | countdown() -> 46 | application:start(cecho), 47 | cecho:cbreak(), 48 | cecho:noecho(), 49 | cecho:curs_set(?ceCURS_INVISIBLE), 50 | cecho:move(1, 1), 51 | Flag = cecho:has_colors(), 52 | cecho:addstr(io_lib:format("Has color: ~p",[Flag])), 53 | print_colors(Flag), 54 | cecho:move(10, 10), 55 | cecho:addstr("Countdown: "), 56 | cecho:refresh(), 57 | count_it_down(10), 58 | cecho:curs_set(?ceCURS_NORMAL), 59 | timer:sleep(2000), 60 | application:stop(cecho). 61 | 62 | count_it_down(S) when S =< 0 -> 63 | cecho:move(10, 22), 64 | cecho:addstr("BOOOOM!"), 65 | cecho:refresh(); 66 | count_it_down(S) -> 67 | cecho:move(10+S, 22), 68 | {X, Y} = cecho:getyx(), 69 | {MX, MY} = cecho:getmaxyx(), 70 | cecho:addstr(io_lib:format("~p",[S])), 71 | cecho:move(22,22), 72 | cecho:addstr(io_lib:format("~p:~p (~p:~p)",[X,Y,MX,MY])), 73 | cecho:refresh(), 74 | timer:sleep(1000), 75 | count_it_down(S-1). 76 | 77 | print_colors(false) -> ok; 78 | print_colors(true) -> 79 | cecho:start_color(), 80 | cecho:init_pair(1, ?ceCOLOR_RED, ?ceCOLOR_BLACK), 81 | cecho:attron(?ceA_BOLD bor ?ceCOLOR_PAIR(1)), 82 | cecho:move(2,1), 83 | cecho:addstr("Colored!"), 84 | cecho:refresh(), 85 | cecho:attroff(?ceA_BOLD bor ?ceCOLOR_PAIR(1)), 86 | ok. 87 | 88 | %% 89 | %% Simple example to show usage 90 | %% 91 | simple() -> 92 | application:start(cecho), 93 | ok = cecho:nocbreak(), 94 | ok = cecho:cbreak(), 95 | ok = cecho:echo(), 96 | ok = cecho:noecho(), 97 | ok = cecho:curs_set(?ceCURS_INVISIBLE), 98 | ok = cecho:move(7, 10), 99 | ok = cecho:addch(45), 100 | ok = cecho:addch(45), 101 | ok = cecho:move(7, 12), 102 | ok = cecho:addstr(" Information ----"), 103 | ok = cecho:move(8, 10), 104 | {Row, Col} = cecho:getyx(), 105 | {MRow, MCol} = cecho:getmaxyx(), 106 | ok = cecho:addstr(io_lib:format("Row:~p Col:~p MaxRow:~p MaxCol:~p", 107 | [Row,Col,MRow,MCol])), 108 | case cecho:has_colors() of 109 | true -> 110 | cecho:start_color(), 111 | ok = cecho:init_pair(1, ?ceCOLOR_BLUE, ?ceCOLOR_WHITE), 112 | ok = cecho:init_pair(2, ?ceCOLOR_GREEN, ?ceCOLOR_YELLOW), 113 | cecho:move(9, 10), 114 | cecho:attron(?ceCOLOR_PAIR(1) bor ?ceA_BOLD bor ?ceA_UNDERLINE), 115 | cecho:addstr(" Has Colors! "), 116 | cecho:attron(?ceCOLOR_PAIR(2)), 117 | cecho:addstr(" Yes!! "), 118 | cecho:attroff(?ceCOLOR_PAIR(1) bor ?ceCOLOR_PAIR(2) bor ?ceA_BOLD bor ?ceA_UNDERLINE); 119 | false -> 120 | cecho:move(9, 10), 121 | cecho:addstr(" No colors :( ") 122 | end, 123 | cecho:addch($!), 124 | ok = cecho:refresh(), 125 | timer:sleep(5000), 126 | cecho:curs_set(?ceCURS_NORMAL), 127 | application:stop(cecho). 128 | 129 | %% 130 | %% Fun example 131 | %% 132 | colors() -> 133 | application:start(cecho), 134 | ok = cecho:cbreak(), 135 | ok = cecho:noecho(), 136 | ok = cecho:curs_set(?ceCURS_INVISIBLE), 137 | ok = cecho:start_color(), 138 | ok = cecho:init_pair(1, ?ceCOLOR_BLACK, ?ceCOLOR_RED), 139 | ok = cecho:init_pair(2, ?ceCOLOR_BLACK, ?ceCOLOR_GREEN), 140 | ok = cecho:init_pair(3, ?ceCOLOR_BLACK, ?ceCOLOR_YELLOW), 141 | ok = cecho:init_pair(4, ?ceCOLOR_BLACK, ?ceCOLOR_BLUE), 142 | ok = cecho:init_pair(5, ?ceCOLOR_BLACK, ?ceCOLOR_MAGENTA), 143 | ok = cecho:init_pair(6, ?ceCOLOR_BLACK, ?ceCOLOR_CYAN), 144 | ok = cecho:init_pair(7, ?ceCOLOR_BLACK, ?ceCOLOR_WHITE), 145 | ok = cecho:init_pair(8, ?ceCOLOR_BLACK, ?ceCOLOR_BLACK), 146 | rand_seed(), 147 | {MaxRow, MaxCol} = cecho:getmaxyx(), 148 | cecho:move(10,10), 149 | cecho:addstr(io_lib:format("Max Row: ~p, Max Col: ~p",[MaxRow, MaxCol])), 150 | cecho:move(0, 0), 151 | cecho:addch($@), 152 | cecho:move(MaxRow-1, 0), 153 | cecho:addch($@), 154 | cecho:move(0, MaxCol-1), 155 | cecho:addch($@), 156 | cecho:move(MaxRow-1, MaxCol-1), 157 | cecho:addch($@), 158 | cecho:refresh(), 159 | timer:sleep(2000), 160 | do_colors(MaxRow, MaxCol, 2000), 161 | application:stop(cecho). 162 | 163 | do_colors(_,_,0) -> ok; 164 | do_colors(MR,MC,N) -> 165 | ch_colors(MR,MC,1000), 166 | cecho:refresh(), 167 | timer:sleep(100), 168 | do_colors(MR, MC, N-1). 169 | 170 | ch_colors(_,_,0) -> ok; 171 | ch_colors(MR, MC, N) -> 172 | R = rand_uniform(MR)-1, 173 | C = rand_uniform(MC)-1, 174 | CN = rand_uniform(8), 175 | cecho:attron(?ceCOLOR_PAIR(CN)), 176 | cecho:move(R, C), 177 | cecho:addch($ ), 178 | cecho:move(R, C), 179 | ch_colors(MR, MC, N-1). 180 | 181 | -ifdef(random_module_available). 182 | rand_seed() -> 183 | random:seed(os:timestamp()). 184 | rand_uniform(N) -> 185 | random:uniform(N). 186 | -else. 187 | rand_seed() -> 188 | ok. 189 | rand_uniform(N) -> 190 | rand:uniform(N). 191 | -endif. 192 | 193 | %% 194 | %% Simply puts an @ character somewhere. If you go out of bounds you crash 195 | %% 196 | pos(Y, X) -> 197 | application:start(cecho), 198 | ok = cecho:cbreak(), 199 | ok = cecho:curs_set(?ceCURS_INVISIBLE), 200 | cecho:move(Y, X), 201 | cecho:addstr("@"), 202 | cecho:refresh(), 203 | timer:sleep(2000), 204 | application:stop(cecho). 205 | 206 | %% 207 | %% Prints a number continuously as another io thread is waiting for keyinput 208 | %% 209 | input() -> 210 | application:start(cecho), 211 | ok = cecho:cbreak(), 212 | ok = cecho:noecho(), 213 | ok = cecho:curs_set(?ceCURS_INVISIBLE), 214 | ok = cecho:keypad(?ceSTDSCR, true), 215 | spawn_link(?MODULE, input_counter, [0]), 216 | cecho:mvaddstr(9, 10, "Enter: "), 217 | cecho:refresh(), 218 | input_reader(). 219 | 220 | input_reader() -> 221 | P = cecho:getch(), 222 | case P of 223 | $q -> 224 | application:stop(cecho); 225 | ?ceKEY_F(1) -> 226 | halt(); 227 | _ -> 228 | cecho:mvaddstr(9, 17, io_lib:format("~p ",[P])), 229 | cecho:refresh(), 230 | input_reader() 231 | end. 232 | 233 | input_counter(N) -> 234 | cecho:mvaddstr(10, 10, io_lib:format("# ~p",[N])), 235 | cecho:refresh(), 236 | timer:sleep(100), 237 | input_counter(N+1). 238 | 239 | %% 240 | %% cursmove - move the '@' around the screen with the arrow keys. 'q' to quit. 241 | %% 242 | cursmove() -> 243 | application:start(cecho), 244 | cecho:cbreak(), 245 | cecho:noecho(), 246 | cecho:curs_set(?ceCURS_INVISIBLE), 247 | cecho:keypad(?ceSTDSCR, true), 248 | cecho:mvaddch(10, 10, $@), 249 | cecho:move(10,10), 250 | cecho:refresh(), 251 | moveloop(cecho:getch()). 252 | moveloop(K) when K == ?ceKEY_F(1) -> 253 | halt(); 254 | moveloop(?ceKEY_ESC) -> 255 | application:stop(cecho); 256 | moveloop(C) -> 257 | case C of 258 | ?ceKEY_UP -> mv(-1, 0); 259 | ?ceKEY_DOWN -> mv(1, 0); 260 | ?ceKEY_RIGHT -> mv(0, 1); 261 | ?ceKEY_LEFT -> mv(0, -1); 262 | _ -> ok 263 | end, 264 | cecho:refresh(), 265 | moveloop(cecho:getch()). 266 | 267 | mv(OffsetY, OffsetX) -> 268 | {CY, CX} = cecho:getyx(), 269 | FinalY = CY+(OffsetY), 270 | FinalX = CX+(OffsetX), 271 | cecho:mvaddch(FinalY,FinalX,$@), 272 | cecho:move(FinalY, FinalX). 273 | 274 | %% 275 | %% helloworld - bounce "Hello World!" on the end of the screen 276 | %% 277 | helloworld() -> 278 | %% Start application 279 | application:start(cecho), 280 | %% Set attributes 281 | cecho:cbreak(), 282 | cecho:noecho(), 283 | cecho:curs_set(?ceCURS_INVISIBLE), 284 | %% Write initial string... 285 | cecho:mvaddstr(0, 0, "Hello World!"), 286 | cecho:refresh(), 287 | %% Start the process that will "move" the string 288 | Mover = spawn(fun() -> mvhello() end), 289 | ctrl(Mover). 290 | 291 | ctrl(Mover) -> 292 | %% get key-input 293 | C = cecho:getch(), 294 | case C of 295 | $q -> 296 | %% If we get a 'q' then exit the mover and stop cecho 297 | exit(Mover, normal), 298 | application:stop(cecho), 299 | erlang:halt(); 300 | _ -> 301 | %% ignore anything else 302 | ctrl(Mover) 303 | end. 304 | 305 | %% start the mover 306 | mvhello() -> mvhello(0, 0, 1, 1). 307 | %% take previous pos and direction and print out new string 308 | mvhello(PrevY, PrevX, DirY, DirX) -> 309 | %% "erase" previous position 310 | cecho:mvaddstr(PrevY, PrevX, " "), 311 | %% calculate new position and direction 312 | {NewY, NewX, NewDirY, NewDirX} = 313 | calc_new_pos(PrevY, PrevX, DirY, DirX), 314 | %% "move" the text to new position 315 | cecho:mvaddstr(NewY, NewX, "Hello World!"), 316 | %% update the screen to show the change 317 | cecho:refresh(), 318 | %% do it again! 319 | timer:sleep(50), 320 | mvhello(NewY, NewX, NewDirY, NewDirX). 321 | 322 | calc_new_pos(Py, Px, Dy, Dx) -> 323 | %% get max coords of the screen 324 | {My, Mx} = cecho:getmaxyx(), 325 | %% calc new vertical position and new direction 326 | {NewPy, NewDy} = 327 | if (Py+(Dy) >= My) orelse (Py+(Dy) < 0) -> 328 | {Py+(Dy*-1), Dy*-1}; 329 | true -> 330 | {Py+(Dy), Dy} 331 | end, 332 | %% calc new horizontal position and new direction 333 | %% take string length into account 334 | {NewPx, NewDx} = 335 | if (Px+(Dx)+12 >= Mx) orelse (Px+(Dx) < 0) -> 336 | {Px+(Dx*-1), Dx*-1}; 337 | true -> 338 | {Px+(Dx), Dx} 339 | end, 340 | {NewPy, NewPx, NewDy, NewDx}. 341 | -------------------------------------------------------------------------------- /src/cecho_srv.erl: -------------------------------------------------------------------------------- 1 | %%============================================================================== 2 | %% Copyright (c) 2017, Mazen Harake 3 | %% All rights reserved. 4 | %% 5 | %% Redistribution and use in source and binary forms, with or without 6 | %% modification, are permitted provided that the following conditions are met: 7 | %% 8 | %% * Redistributions of source code must retain the above copyright notice, 9 | %% this list of conditions and the following disclaimer. 10 | %% * Redistributions in binary form must reproduce the above copyright 11 | %% notice, this list of conditions and the following disclaimer in the 12 | %% documentation and/or other materials provided with the distribution. 13 | %% 14 | %% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | %% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | %% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | %% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | %% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | %% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | %% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | %% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | %% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | %% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | %% POSSIBILITY OF SUCH DAMAGE. 25 | %%============================================================================== 26 | 27 | -module(cecho_srv). 28 | 29 | -author('mazen.harake@gmail.com'). 30 | 31 | -behaviour(gen_server). 32 | -include("cecho.hrl"). 33 | -include("cecho_commands.hrl"). 34 | 35 | %% Behaviour Callbacks 36 | -export([init/1, terminate/2, handle_call/3, handle_cast/2, handle_info/2, 37 | code_change/3]). 38 | 39 | %% Module API 40 | -export([start_link/0, call/2, getch/0]). 41 | 42 | %% Records 43 | -record(state, { port, getch, observer }). 44 | 45 | %% ============================================================================= 46 | %% Module API 47 | %% ============================================================================= 48 | start_link() -> 49 | gen_server:start_link({local, ?MODULE}, ?MODULE, no_args, []). 50 | 51 | call(Cmd, Args) -> 52 | gen_server:call(?MODULE, {call, Cmd, Args}, infinity). 53 | 54 | getch() -> 55 | gen_server:call(?MODULE, getch, infinity). 56 | 57 | %% ============================================================================= 58 | %% Behaviour Callbacks 59 | %% ============================================================================= 60 | init(no_args) -> 61 | process_flag(trap_exit, true), 62 | case load_driver() of 63 | ok -> 64 | Port = erlang:open_port({spawn, "cecho"}, [binary]), 65 | ok = do_call(Port, ?INITSCR), 66 | ok = do_call(Port, ?WERASE, 0), 67 | ok = do_call(Port, ?REFRESH), 68 | {ok, #state{ port = Port }}; 69 | {error, ErrorCode} -> 70 | exit({driver_error, erl_ddll:format_error(ErrorCode)}) 71 | end. 72 | 73 | handle_call({call, Cmd, Args}, _From, State) -> 74 | {reply, do_call(State#state.port, Cmd, Args), State}; 75 | handle_call(getch, From, #state{ getch = undefined } = State) -> 76 | {noreply, State#state{ getch = From }}; 77 | handle_call(getch, _From, State) -> 78 | {reply, -1, State}. 79 | 80 | terminate(_Reason, State) -> 81 | do_call(State#state.port, ?ENDWIN), 82 | do_call(State#state.port, ?CURS_SET, ?ceCURS_NORMAL), 83 | erlang:port_close(State#state.port), 84 | erl_ddll:unload("cecho"). 85 | 86 | handle_info({_Port, {data, _Binary}}, #state{ getch = undefined } = State) -> 87 | {noreply, State}; 88 | handle_info({_Port, {data, Binary}}, State) -> 89 | gen_server:reply(State#state.getch, binary_to_term(Binary)), 90 | {noreply, State#state{ getch = undefined }}. 91 | 92 | %% @hidden 93 | handle_cast(_, State) -> 94 | {noreply, State}. 95 | 96 | %% @hidden 97 | code_change(_, State, _) -> 98 | {ok, State}. 99 | 100 | %% ============================================================================= 101 | %% Internal Functions 102 | %% ============================================================================= 103 | do_call(Port, Cmd) -> 104 | do_call(Port, Cmd, undefined). 105 | 106 | do_call(Port, Cmd, Args) -> 107 | binary_to_term(erlang:port_control(Port, Cmd, term_to_binary(Args))). 108 | 109 | load_driver() -> 110 | Dir = case code:priv_dir(cecho) of 111 | {error, bad_name} -> 112 | filename:dirname(code:which(?MODULE)) ++ "/../priv"; 113 | D -> 114 | D 115 | end, 116 | erl_ddll:load(Dir, "cecho"). 117 | --------------------------------------------------------------------------------