Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 9ef924df34
Fetching contributors…

Cannot retrieve contributors at this time

file 459 lines (378 sloc) 11.058 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>

#include "term.h"
#include "termbox.h"

struct cellbuf {
unsigned int width;
unsigned int height;
struct tb_cell *cells;
};

#define CELL(buf, x, y) (buf)->cells[(y) * (buf)->width + (x)]
#define IS_CURSOR_HIDDEN(cx, cy) (cx == -1 || cy == -1)

#define LAST_COORD_INIT 0xFFFFFFFE

static struct termios orig_tios;

static struct cellbuf back_buffer;
static struct cellbuf front_buffer;
static unsigned int termw;
static unsigned int termh;

static int inputmode = TB_INPUT_ESC;

static struct ringbuffer inbuf;

static FILE *out;
static FILE *in;

static int out_fileno;
static int in_fileno;

static int winch_fds[2];

static unsigned int lastx = LAST_COORD_INIT;
static unsigned int lasty = LAST_COORD_INIT;
static int cursor_x = -1;
static int cursor_y = -1;

static uint16_t background = TB_BLACK;
static uint16_t foreground = TB_WHITE;

static void cellbuf_init(struct cellbuf *buf, unsigned int width, unsigned int height);
static void cellbuf_resize(struct cellbuf *buf, unsigned int width, unsigned int height);
static void cellbuf_clear(struct cellbuf *buf);
static void cellbuf_free(struct cellbuf *buf);

static void update_size();
static void update_term_size();
static void send_attr(uint16_t fg, uint16_t bg);
static void send_char(unsigned int x, unsigned int y, uint32_t c);
static void send_clear();
static void sigwinch_handler(int xxx);
static int wait_fill_event(struct tb_event *event, struct timeval *timeout);

/* may happen in a different thread */
static volatile int buffer_size_change_request;

/* -------------------------------------------------------- */

int tb_init()
{
out = fopen("/dev/tty", "w");
in = fopen("/dev/tty", "r");

if (!out || !in)
return TB_EFAILED_TO_OPEN_TTY;

out_fileno = fileno(out);
in_fileno = fileno(in);

if (init_term() < 0)
return TB_EUNSUPPORTED_TERMINAL;

if (pipe(winch_fds) < 0)
return TB_EPIPE_TRAP_ERROR;

struct sigaction sa;
sa.sa_handler = sigwinch_handler;
sa.sa_flags = 0;
sigaction(SIGWINCH, &sa, 0);

tcgetattr(out_fileno, &orig_tios);

struct termios tios;
memcpy(&tios, &orig_tios, sizeof(tios));

tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                           | INLCR | IGNCR | ICRNL | IXON);
tios.c_oflag &= ~OPOST;
tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tios.c_cflag &= ~(CSIZE | PARENB);
tios.c_cflag |= CS8;
tios.c_cc[VMIN] = 0;
tios.c_cc[VTIME] = 0;
tcsetattr(out_fileno, TCSAFLUSH, &tios);

fputs(funcs[T_ENTER_CA], out);
fputs(funcs[T_ENTER_KEYPAD], out);
fputs(funcs[T_HIDE_CURSOR], out);
send_clear();

update_term_size();
cellbuf_init(&back_buffer, termw, termh);
cellbuf_init(&front_buffer, termw, termh);
cellbuf_clear(&back_buffer);
cellbuf_clear(&front_buffer);
init_ringbuffer(&inbuf, 4096);

return 0;
}

void tb_shutdown()
{
fputs(funcs[T_SHOW_CURSOR], out);
fputs(funcs[T_SGR0], out);
fputs(funcs[T_CLEAR_SCREEN], out);
fputs(funcs[T_EXIT_CA], out);
fputs(funcs[T_EXIT_KEYPAD], out);
fflush(out);
tcsetattr(out_fileno, TCSAFLUSH, &orig_tios);

fclose(out);
fclose(in);
close(winch_fds[0]);
close(winch_fds[1]);

cellbuf_free(&back_buffer);
cellbuf_free(&front_buffer);
free_ringbuffer(&inbuf);
}

void tb_present()
{
unsigned int x,y;
struct tb_cell *back, *front;

/* invalidate cursor position */
lastx = LAST_COORD_INIT;
lasty = LAST_COORD_INIT;

if (buffer_size_change_request) {
update_size();
buffer_size_change_request = 0;
}

for (y = 0; y < front_buffer.height; ++y) {
for (x = 0; x < front_buffer.width; ++x) {
back = &CELL(&back_buffer, x, y);
front = &CELL(&front_buffer, x, y);
if (memcmp(back, front, sizeof(struct tb_cell)) == 0)
continue;
send_attr(back->fg, back->bg);
send_char(x, y, back->ch);
memcpy(front, back, sizeof(struct tb_cell));
}
}
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
fprintf(out, funcs[T_MOVE_CURSOR], cursor_y+1, cursor_x+1);
fflush(out);
}

void tb_set_cursor(int cx, int cy)
{
if (IS_CURSOR_HIDDEN(cursor_x, cursor_y) && !IS_CURSOR_HIDDEN(cx, cy))
fputs(funcs[T_SHOW_CURSOR], out);

if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y) && IS_CURSOR_HIDDEN(cx, cy))
fputs(funcs[T_HIDE_CURSOR], out);

cursor_x = cx;
cursor_y = cy;
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
fprintf(out, funcs[T_MOVE_CURSOR], cursor_y+1, cursor_x+1);
}

void tb_put_cell(unsigned int x, unsigned int y, const struct tb_cell *cell)
{
if (x >= back_buffer.width || y >= back_buffer.height)
return;
CELL(&back_buffer, x, y) = *cell;
}

void tb_change_cell(unsigned int x, unsigned int y, uint32_t ch, uint16_t fg, uint16_t bg)
{
struct tb_cell c = {ch, fg, bg};
tb_put_cell(x, y, &c);
}

void tb_blit(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const struct tb_cell *cells)
{
if (x+w > back_buffer.width || y+h > back_buffer.height)
return;

unsigned int sy;
struct tb_cell *dst = &CELL(&back_buffer, x, y);
size_t size = sizeof(struct tb_cell) * w;

for (sy = 0; sy < h; ++sy) {
memcpy(dst, cells, size);
dst += back_buffer.width;
cells += w;
}
}

int tb_poll_event(struct tb_event *event)
{
return wait_fill_event(event, 0);
}

int tb_peek_event(struct tb_event *event, unsigned int timeout)
{
struct timeval tv;
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout - (tv.tv_sec * 1000)) * 1000;
return wait_fill_event(event, &tv);
}

unsigned int tb_width()
{
return termw;
}

unsigned int tb_height()
{
return termh;
}

void tb_clear()
{
if (buffer_size_change_request) {
update_size();
buffer_size_change_request = 0;
}
cellbuf_clear(&back_buffer);
}

int tb_select_input_mode(int mode)
{
if (mode)
inputmode = mode;
return inputmode;
}

void tb_set_clear_attributes(uint16_t fg, uint16_t bg)
{
foreground = fg;
background = bg;
}

/* -------------------------------------------------------- */

static void cellbuf_init(struct cellbuf *buf, unsigned int width, unsigned int height)
{
buf->cells = (struct tb_cell*)malloc(sizeof(struct tb_cell) * width * height);
assert(buf->cells);
buf->width = width;
buf->height = height;
}

static void cellbuf_resize(struct cellbuf *buf, unsigned int width, unsigned int height)
{
if (buf->width == width && buf->height == height)
return;

unsigned int oldw = buf->width;
unsigned int oldh = buf->height;
struct tb_cell *oldcells = buf->cells;

cellbuf_init(buf, width, height);
cellbuf_clear(buf);

unsigned int minw = (width < oldw) ? width : oldw;
unsigned int minh = (height < oldh) ? height : oldh;
unsigned int i;

for (i = 0; i < minh; ++i) {
struct tb_cell *csrc = oldcells + (i * oldw);
struct tb_cell *cdst = buf->cells + (i * width);
memcpy(cdst, csrc, sizeof(struct tb_cell) * minw);
}

free(oldcells);
}

static void cellbuf_clear(struct cellbuf *buf)
{
unsigned int i;
unsigned int ncells = buf->width * buf->height;

for (i = 0; i < ncells; ++i) {
buf->cells[i].ch = ' ';
buf->cells[i].fg = foreground;
buf->cells[i].bg = background;
}
}

static void cellbuf_free(struct cellbuf *buf)
{
free(buf->cells);
}

static void get_term_size(int *w, int *h)
{
struct winsize sz;
memset(&sz, 0, sizeof(sz));

ioctl(out_fileno, TIOCGWINSZ, &sz);

if (w) *w = sz.ws_col;
if (h) *h = sz.ws_row;
}

static void update_term_size()
{
struct winsize sz;
memset(&sz, 0, sizeof(sz));

ioctl(out_fileno, TIOCGWINSZ, &sz);

termw = sz.ws_col;
termh = sz.ws_row;
}

static void send_attr(uint16_t fg, uint16_t bg)
{
#define LAST_ATTR_INIT 0xFFFF
static uint16_t lastfg = LAST_ATTR_INIT, lastbg = LAST_ATTR_INIT;
if (fg != lastfg || bg != lastbg) {
fputs(funcs[T_SGR0], out);
/* TODO: get rid of fprintf */
fprintf(out, funcs[T_SGR], fg & 0x0F, bg & 0x0F);
if (fg & TB_BOLD)
fputs(funcs[T_BOLD], out);
if (bg & TB_BOLD)
fputs(funcs[T_BLINK], out);
if (fg & TB_UNDERLINE)
fputs(funcs[T_UNDERLINE], out);

lastfg = fg;
lastbg = bg;
}
}

static void send_char(unsigned int x, unsigned int y, uint32_t c)
{
char buf[7];
int bw = utf8_unicode_to_char(buf, c);
buf[bw] = '\0';
if (x-1 != lastx || y != lasty)
fprintf(out, funcs[T_MOVE_CURSOR], y+1, x+1); /* TODO: get rid of fprintf */
lastx = x; lasty = y;
fputs(buf, out);
}

static void send_clear()
{
send_attr(foreground, background);
fputs(funcs[T_CLEAR_SCREEN], out);
if (!IS_CURSOR_HIDDEN(cursor_x, cursor_y))
fprintf(out, funcs[T_MOVE_CURSOR], cursor_y+1, cursor_x+1);
fflush(out);

/* we need to invalidate cursor position too and these two vars are
* used only for simple cursor positioning optimization, cursor
* actually may be in the correct place, but we simply discard
* optimization once and it gives us simple solution for the case when
* cursor moved */
lastx = LAST_COORD_INIT;
lasty = LAST_COORD_INIT;
}

static void sigwinch_handler(int xxx)
{
const int zzz = 1;
write(winch_fds[1], &zzz, sizeof(int));
}

static void update_size()
{
update_term_size();
cellbuf_resize(&back_buffer, termw, termh);
cellbuf_resize(&front_buffer, termw, termh);
cellbuf_clear(&front_buffer);
send_clear();
}

static int wait_fill_event(struct tb_event *event, struct timeval *timeout)
{
/* ;-) */
#define ENOUGH_DATA_FOR_INPUT_PARSING 128
int result;
char buf[ENOUGH_DATA_FOR_INPUT_PARSING];
fd_set events;
memset(event, 0, sizeof(struct tb_event));

/* try to extract event from input buffer, return on success */
event->type = TB_EVENT_KEY;
if (extract_event(event, &inbuf, inputmode) == 0)
return TB_EVENT_KEY;

/* it looks like input buffer is incomplete, let's try the short path */
size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
clearerr(in);
if (r > 0) {
if (ringbuffer_free_space(&inbuf) < r)
return -1;
ringbuffer_push(&inbuf, buf, r);
if (extract_event(event, &inbuf, inputmode) == 0)
return TB_EVENT_KEY;
}

/* no stuff in FILE's internal buffer, block in select */
while (1) {
FD_ZERO(&events);
FD_SET(in_fileno, &events);
FD_SET(winch_fds[0], &events);
int maxfd = (winch_fds[0] > in_fileno) ? winch_fds[0] : in_fileno;
result = select(maxfd+1, &events, 0, 0, timeout);
if (!result)
return 0;

if (FD_ISSET(in_fileno, &events)) {
event->type = TB_EVENT_KEY;
size_t r = fread(buf, 1, ENOUGH_DATA_FOR_INPUT_PARSING, in);
if (r < ENOUGH_DATA_FOR_INPUT_PARSING && feof(in))
clearerr(in);
if (r == 0)
continue;
/* if there is no free space in input buffer, return error */
if (ringbuffer_free_space(&inbuf) < r)
return -1;
/* fill buffer */
ringbuffer_push(&inbuf, buf, r);
if (extract_event(event, &inbuf, inputmode) == 0)
return TB_EVENT_KEY;
}
if (FD_ISSET(winch_fds[0], &events)) {
event->type = TB_EVENT_RESIZE;
int zzz = 0;
read(winch_fds[0], &zzz, sizeof(int));
buffer_size_change_request = 1;
get_term_size(&event->w, &event->h);
return TB_EVENT_RESIZE;
}
}
}
Something went wrong with that request. Please try again.