Skip to content

Commit

Permalink
1136 - switch from ncurses to https://github.com/nsf/termbox
Browse files Browse the repository at this point in the history
  • Loading branch information
akkartik committed Apr 23, 2015
1 parent e89eaad commit 5f12852
Show file tree
Hide file tree
Showing 12 changed files with 1,651 additions and 26 deletions.
63 changes: 44 additions & 19 deletions cpp/070display
@@ -1,21 +1,18 @@
//: Text-mode cursor primitives. Currently thin wrappers around ncurses calls.
//: Mu starts out at the 'console' where lines wrap and scrolling is
//: automatic, where keys aren't read until pressing <enter>.
//: This file provides mechanisms for opening a 'display' and taking raw
//: charge of the cursor and keyboard.

:(before "End Includes")
#include<ncurses.h>
//: Take charge of the text-mode display and keyboard.

//:: Display management

:(before "End Globals")
size_t Display_row = 0, Display_column = 0;

:(before "End Primitive Recipe Declarations")
SWITCH_TO_DISPLAY,
:(before "End Primitive Recipe Numbers")
Recipe_number["switch-to-display"] = SWITCH_TO_DISPLAY;
:(before "End Primitive Recipe Implementations")
case SWITCH_TO_DISPLAY: {
initscr();
tb_init();
Display_row = Display_column = 0;
break;
}

Expand All @@ -25,7 +22,7 @@ RETURN_TO_CONSOLE,
Recipe_number["return-to-console"] = RETURN_TO_CONSOLE;
:(before "End Primitive Recipe Implementations")
case RETURN_TO_CONSOLE: {
endwin();
tb_shutdown();
break;
}

Expand All @@ -35,7 +32,8 @@ CLEAR_DISPLAY,
Recipe_number["clear-display"] = CLEAR_DISPLAY;
:(before "End Primitive Recipe Implementations")
case CLEAR_DISPLAY: {
clear();
tb_clear();
Display_row = Display_column = 0;
break;
}

Expand All @@ -45,7 +43,12 @@ CLEAR_LINE_ON_DISPLAY,
Recipe_number["clear-line-on-display"] = CLEAR_LINE_ON_DISPLAY;
:(before "End Primitive Recipe Implementations")
case CLEAR_LINE_ON_DISPLAY: {
clrtoeol();
size_t width = tb_width();
for (size_t x = Display_column; x < width; ++x) {
tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_DEFAULT);
}
tb_set_cursor(Display_column, Display_row);
tb_present();
break;
}

Expand All @@ -56,7 +59,24 @@ Recipe_number["print-character-to-display"] = PRINT_CHARACTER_TO_DISPLAY;
:(before "End Primitive Recipe Implementations")
case PRINT_CHARACTER_TO_DISPLAY: {
vector<int> arg = read_memory(instructions[pc].ingredients[0]);
addch(arg[0]);
int h=tb_height(), w=tb_width();
size_t height = (h >= 0) ? h : 0;
size_t width = (w >= 0) ? w : 0;
if (arg[0] == '\n') {
if (Display_row < height) {
Display_column = 0;
++Display_row;
tb_set_cursor(Display_column, Display_row);
tb_present();
}
break;
}
tb_change_cell(Display_column, Display_row, arg[0], TB_WHITE, TB_DEFAULT);
if (Display_column < width) {
Display_column++;
tb_set_cursor(Display_column, Display_row);
}
tb_present();
break;
}

Expand All @@ -66,13 +86,11 @@ CURSOR_POSITION_ON_DISPLAY,
Recipe_number["cursor-position-on-display"] = CURSOR_POSITION_ON_DISPLAY;
:(before "End Primitive Recipe Implementations")
case CURSOR_POSITION_ON_DISPLAY: {
size_t cursor_row = 0, cursor_column = 0;
getyx(stdscr, cursor_row, cursor_column);
vector<int> row;
row.push_back(cursor_row);
row.push_back(Display_row);
write_memory(instructions[pc].products[0], row);
vector<int> column;
column.push_back(cursor_column);
column.push_back(Display_column);
write_memory(instructions[pc].products[1], column);
break;
}
Expand All @@ -85,7 +103,10 @@ Recipe_number["move-cursor-on-display"] = MOVE_CURSOR_ON_DISPLAY;
case MOVE_CURSOR_ON_DISPLAY: {
vector<int> row = read_memory(instructions[pc].ingredients[0]);
vector<int> column = read_memory(instructions[pc].ingredients[1]);
move(row[0], column[0]);
Display_row = row[0];
Display_column = column[0];
tb_set_cursor(Display_column, Display_row);
tb_present();
break;
}

Expand All @@ -97,6 +118,10 @@ WAIT_FOR_KEY_FROM_KEYBOARD,
Recipe_number["wait-for-key-from-keyboard"] = WAIT_FOR_KEY_FROM_KEYBOARD;
:(before "End Primitive Recipe Implementations")
case WAIT_FOR_KEY_FROM_KEYBOARD: {
getch();
struct tb_event event;
tb_poll_event(&event);
break;
}

:(before "End Includes")
#include"termbox/termbox.h"
5 changes: 0 additions & 5 deletions cpp/display.mu
Expand Up @@ -2,11 +2,6 @@ recipe main [
switch-to-display
print-character-to-display 97:literal
1:integer/raw, 2:integer/raw <- cursor-position-on-display
$print 1:integer/raw
$print [, ]
$print 2:integer/raw
$print [
]
wait-for-key-from-keyboard
clear-display
move-cursor-on-display 0:literal, 4:literal
Expand Down
8 changes: 6 additions & 2 deletions cpp/makefile
@@ -1,6 +1,6 @@
mu: makefile enumerate/enumerate tangle/tangle mu.cc
mu: makefile enumerate/enumerate tangle/tangle mu.cc termbox/libtermbox.a
@make autogenerated_lists >/dev/null
g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc -lncurses -o mu
g++ -g -Wall -Wextra -fno-strict-aliasing mu.cc termbox/libtermbox.a -o mu

# To see what the program looks like after all layers have been applied, read
# mu.cc
Expand All @@ -14,6 +14,9 @@ enumerate/enumerate:
tangle/tangle:
cd tangle && make && ./tangle test

termbox/libtermbox.a:
cd termbox && make

# auto-generated files; by convention they end in '_list'.
.PHONY: autogenerated_lists test clena
autogenerated_lists: mu.cc function_list test_list
Expand All @@ -34,4 +37,5 @@ clena: clean
clean:
cd enumerate && make clean
cd tangle && make clean
cd termbox && make clean
rm -rf mu.cc core.mu mu *_list
19 changes: 19 additions & 0 deletions cpp/termbox/COPYING
@@ -0,0 +1,19 @@
Copyright (C) 2010-2013 nsf <no.smile.face@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
2 changes: 2 additions & 0 deletions cpp/termbox/README
@@ -0,0 +1,2 @@
Fork of https://github.com/nsf/termbox as of 2015-04-22
git hash 7c154d98a7d9207d768ee0a8e519ede74c0105cf
78 changes: 78 additions & 0 deletions cpp/termbox/bytebuffer.inl
@@ -0,0 +1,78 @@
struct bytebuffer {
char *buf;
int len;
int cap;
};

static void bytebuffer_reserve(struct bytebuffer *b, int cap) {
if (b->cap >= cap) {
return;
}

// prefer doubling capacity
if (b->cap * 2 >= cap) {
cap = b->cap * 2;
}

char *newbuf = malloc(cap);
if (b->len > 0) {
// copy what was there, b->len > 0 assumes b->buf != null
memcpy(newbuf, b->buf, b->len);
}
if (b->buf) {
// in case there was an allocated buffer, free it
free(b->buf);
}
b->buf = newbuf;
b->cap = cap;
}

static void bytebuffer_init(struct bytebuffer *b, int cap) {
b->cap = 0;
b->len = 0;
b->buf = 0;

if (cap > 0) {
b->cap = cap;
b->buf = malloc(cap); // just assume malloc works always
}
}

static void bytebuffer_free(struct bytebuffer *b) {
if (b->buf)
free(b->buf);
}

static void bytebuffer_clear(struct bytebuffer *b) {
b->len = 0;
}

static void bytebuffer_append(struct bytebuffer *b, const char *data, int len) {
bytebuffer_reserve(b, b->len + len);
memcpy(b->buf + b->len, data, len);
b->len += len;
}

static void bytebuffer_puts(struct bytebuffer *b, const char *str) {
bytebuffer_append(b, str, strlen(str));
}

static void bytebuffer_resize(struct bytebuffer *b, int len) {
bytebuffer_reserve(b, len);
b->len = len;
}

static void bytebuffer_flush(struct bytebuffer *b, int fd) {
write(fd, b->buf, b->len);
bytebuffer_clear(b);
}

static void bytebuffer_truncate(struct bytebuffer *b, int n) {
if (n <= 0)
return;
if (n > b->len)
n = b->len;
const int nmove = b->len - n;
memmove(b->buf, b->buf+n, nmove);
b->len -= n;
}
132 changes: 132 additions & 0 deletions cpp/termbox/input.inl
@@ -0,0 +1,132 @@
// if s1 starts with s2 returns true, else false
// len is the length of s1
// s2 should be null-terminated
static bool starts_with(const char *s1, int len, const char *s2)
{
int n = 0;
while (*s2 && n < len) {
if (*s1++ != *s2++)
return false;
n++;
}
return *s2 == 0;
}

// convert escape sequence to event, and return consumed bytes on success (failure == 0)
static int parse_escape_seq(struct tb_event *event, const char *buf, int len)
{
if (len >= 6 && starts_with(buf, len, "\033[M")) {

switch (buf[3] & 3) {
case 0:
if (buf[3] == 0x60)
event->key = TB_KEY_MOUSE_WHEEL_UP;
else
event->key = TB_KEY_MOUSE_LEFT;
break;
case 1:
if (buf[3] == 0x61)
event->key = TB_KEY_MOUSE_WHEEL_DOWN;
else
event->key = TB_KEY_MOUSE_MIDDLE;
break;
case 2:
event->key = TB_KEY_MOUSE_RIGHT;
break;
case 3:
event->key = TB_KEY_MOUSE_RELEASE;
break;
default:
return -6;
}
event->type = TB_EVENT_MOUSE; // TB_EVENT_KEY by default

// the coord is 1,1 for upper left
event->x = buf[4] - 1 - 32;
event->y = buf[5] - 1 - 32;

return 6;
}

// it's pretty simple here, find 'starts_with' match and return
// success, else return failure
int i;
for (i = 0; keys[i]; i++) {
if (starts_with(buf, len, keys[i])) {
event->ch = 0;
event->key = 0xFFFF-i;
return strlen(keys[i]);
}
}
return 0;
}

static bool extract_event(struct tb_event *event, struct bytebuffer *inbuf, int inputmode)
{
const char *buf = inbuf->buf;
const int len = inbuf->len;
if (len == 0)
return false;

if (buf[0] == '\033') {
int n = parse_escape_seq(event, buf, len);
if (n != 0) {
bool success = true;
if (n < 0) {
success = false;
n = -n;
}
bytebuffer_truncate(inbuf, n);
return success;
} else {
// it's not escape sequence, then it's ALT or ESC,
// check inputmode
if (inputmode&TB_INPUT_ESC) {
// if we're in escape mode, fill ESC event, pop
// buffer, return success
event->ch = 0;
event->key = TB_KEY_ESC;
event->mod = 0;
bytebuffer_truncate(inbuf, 1);
return true;
}
if (inputmode&TB_INPUT_ALT) {
// if we're in alt mode, set ALT modifier to
// event and redo parsing
event->mod = TB_MOD_ALT;
bytebuffer_truncate(inbuf, 1);
return extract_event(event, inbuf, inputmode);
}
assert(!"never got here");
}
}

// if we're here, this is not an escape sequence and not an alt sequence
// so, it's a FUNCTIONAL KEY or a UNICODE character

// first of all check if it's a functional key
if ((unsigned char)buf[0] <= TB_KEY_SPACE ||
(unsigned char)buf[0] == TB_KEY_BACKSPACE2)
{
// fill event, pop buffer, return success */
event->ch = 0;
event->key = (uint16_t)buf[0];
bytebuffer_truncate(inbuf, 1);
return true;
}

// feh... we got utf8 here

// check if there is all bytes
if (len >= tb_utf8_char_length(buf[0])) {
/* everything ok, fill event, pop buffer, return success */
tb_utf8_char_to_unicode(&event->ch, buf);
event->key = 0;
bytebuffer_truncate(inbuf, tb_utf8_char_length(buf[0]));
return true;
}

// event isn't recognized, perhaps there is not enough bytes in utf8
// sequence
return false;
}

0 comments on commit 5f12852

Please sign in to comment.