filetype: Set "groovy" for Jenkinsfile
[vis.git] / ui-terminal.c
blob9cdfd7e2243b9a71fdc8487548db5aa2544f0dbe
1 #include <unistd.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <strings.h>
5 #include <limits.h>
6 #include <ctype.h>
7 #include <locale.h>
8 #include <poll.h>
9 #include <sys/ioctl.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <fcntl.h>
13 #include <termios.h>
14 #include <errno.h>
16 #include "ui-terminal.h"
17 #include "vis.h"
18 #include "vis-core.h"
19 #include "text.h"
20 #include "util.h"
21 #include "text-util.h"
23 #ifndef DEBUG_UI
24 #define DEBUG_UI 0
25 #endif
27 #if DEBUG_UI
28 #define debug(...) do { printf(__VA_ARGS__); fflush(stdout); } while (0)
29 #else
30 #define debug(...) do { } while (0)
31 #endif
33 #define MAX_WIDTH 1024
34 #define MAX_HEIGHT 1024
35 typedef struct UiTermWin UiTermWin;
37 typedef struct {
38 Ui ui; /* generic ui interface, has to be the first struct member */
39 Vis *vis; /* editor instance to which this ui belongs */
40 UiTermWin *windows; /* all windows managed by this ui */
41 UiTermWin *selwin; /* the currently selected layout */
42 char info[MAX_WIDTH]; /* info message displayed at the bottom of the screen */
43 int width, height; /* terminal dimensions available for all windows */
44 enum UiLayout layout; /* whether windows are displayed horizontally or vertically */
45 TermKey *termkey; /* libtermkey instance to handle keyboard input (stdin or /dev/tty) */
46 size_t ids; /* bit mask of in use window ids */
47 size_t styles_size; /* #bytes allocated for styles array */
48 CellStyle *styles; /* each window has UI_STYLE_MAX different style definitions */
49 size_t cells_size; /* #bytes allocated for 2D grid (grows only) */
50 Cell *cells; /* 2D grid of cells, at least as large as current terminal size */
51 } UiTerm;
53 struct UiTermWin {
54 UiWin uiwin; /* generic interface, has to be the first struct member */
55 UiTerm *ui; /* ui which manages this window */
56 Win *win; /* editor window being displayed */
57 int id; /* unique identifier for this window */
58 int width, height; /* window dimension including status bar */
59 int x, y; /* window position */
60 int sidebar_width; /* width of the sidebar showing line numbers etc. */
61 UiTermWin *next, *prev; /* pointers to neighbouring windows */
62 enum UiOption options; /* display settings for this window */
65 #if CONFIG_CURSES
66 #include "ui-terminal-curses.c"
67 #else
68 #include "ui-terminal-vt100.c"
69 #endif
71 __attribute__((noreturn)) static void ui_die(Ui *ui, const char *msg, va_list ap) {
72 UiTerm *tui = (UiTerm*)ui;
73 ui_term_backend_free(tui);
74 if (tui->termkey)
75 termkey_stop(tui->termkey);
76 vfprintf(stderr, msg, ap);
77 exit(EXIT_FAILURE);
80 __attribute__((noreturn)) static void ui_die_msg(Ui *ui, const char *msg, ...) {
81 va_list ap;
82 va_start(ap, msg);
83 ui_die(ui, msg, ap);
84 va_end(ap);
87 static void ui_window_resize(UiTermWin *win, int width, int height) {
88 debug("ui-win-resize[%s]: %dx%d\n", win->win->file->name ? win->win->file->name : "noname", width, height);
89 bool status = win->options & UI_OPTION_STATUSBAR;
90 win->width = width;
91 win->height = height;
92 view_resize(win->win->view, width - win->sidebar_width, status ? height - 1 : height);
95 static void ui_window_move(UiTermWin *win, int x, int y) {
96 debug("ui-win-move[%s]: (%d, %d)\n", win->win->file->name ? win->win->file->name : "noname", x, y);
97 win->x = x;
98 win->y = y;
101 static bool color_fromstring(UiTerm *ui, CellColor *color, const char *s)
103 if (!s)
104 return false;
105 if (*s == '#' && strlen(s) == 7) {
106 const char *cp;
107 unsigned char r, g, b;
108 for (cp = s + 1; isxdigit((unsigned char)*cp); cp++);
109 if (*cp != '\0')
110 return false;
111 int n = sscanf(s + 1, "%2hhx%2hhx%2hhx", &r, &g, &b);
112 if (n != 3)
113 return false;
114 *color = color_rgb(ui, r, g, b);
115 return true;
116 } else if ('0' <= *s && *s <= '9') {
117 int index = atoi(s);
118 if (index <= 0 || index > 255)
119 return false;
120 *color = color_terminal(ui, index);
121 return true;
124 static const struct {
125 const char *name;
126 CellColor color;
127 } color_names[] = {
128 { "black", CELL_COLOR_BLACK },
129 { "red", CELL_COLOR_RED },
130 { "green", CELL_COLOR_GREEN },
131 { "yellow", CELL_COLOR_YELLOW },
132 { "blue", CELL_COLOR_BLUE },
133 { "magenta", CELL_COLOR_MAGENTA },
134 { "cyan", CELL_COLOR_CYAN },
135 { "white", CELL_COLOR_WHITE },
136 { "default", CELL_COLOR_DEFAULT },
139 for (size_t i = 0; i < LENGTH(color_names); i++) {
140 if (strcasecmp(color_names[i].name, s) == 0) {
141 *color = color_names[i].color;
142 return true;
146 return false;
149 static bool ui_style_define(UiWin *w, int id, const char *style) {
150 UiTermWin *win = (UiTermWin*)w;
151 UiTerm *tui = win->ui;
152 if (id >= UI_STYLE_MAX)
153 return false;
154 if (!style)
155 return true;
156 CellStyle cell_style = tui->styles[win->id * UI_STYLE_MAX + UI_STYLE_DEFAULT];
157 char *style_copy = strdup(style), *option = style_copy;
158 while (option) {
159 while (*option == ' ')
160 option++;
161 char *next = strchr(option, ',');
162 if (next)
163 *next++ = '\0';
164 char *value = strchr(option, ':');
165 if (value)
166 for (*value++ = '\0'; *value == ' '; value++);
167 if (!strcasecmp(option, "reverse")) {
168 cell_style.attr |= CELL_ATTR_REVERSE;
169 } else if (!strcasecmp(option, "bold")) {
170 cell_style.attr |= CELL_ATTR_BOLD;
171 } else if (!strcasecmp(option, "notbold")) {
172 cell_style.attr &= ~CELL_ATTR_BOLD;
173 } else if (!strcasecmp(option, "italics")) {
174 cell_style.attr |= CELL_ATTR_ITALIC;
175 } else if (!strcasecmp(option, "notitalics")) {
176 cell_style.attr &= ~CELL_ATTR_ITALIC;
177 } else if (!strcasecmp(option, "underlined")) {
178 cell_style.attr |= CELL_ATTR_UNDERLINE;
179 } else if (!strcasecmp(option, "notunderlined")) {
180 cell_style.attr &= ~CELL_ATTR_UNDERLINE;
181 } else if (!strcasecmp(option, "blink")) {
182 cell_style.attr |= CELL_ATTR_BLINK;
183 } else if (!strcasecmp(option, "notblink")) {
184 cell_style.attr &= ~CELL_ATTR_BLINK;
185 } else if (!strcasecmp(option, "fore")) {
186 color_fromstring(win->ui, &cell_style.fg, value);
187 } else if (!strcasecmp(option, "back")) {
188 color_fromstring(win->ui, &cell_style.bg, value);
190 option = next;
192 tui->styles[win->id * UI_STYLE_MAX + id] = cell_style;
193 free(style_copy);
194 return true;
197 static void ui_draw_line(UiTerm *tui, int x, int y, char c, enum UiStyle style_id) {
198 if (x < 0 || x >= tui->width || y < 0 || y >= tui->height)
199 return;
200 CellStyle style = tui->styles[style_id];
201 Cell *cells = tui->cells + y * tui->width;
202 while (x < tui->width) {
203 cells[x].data[0] = c;
204 cells[x].data[1] = '\0';
205 cells[x].style = style;
206 x++;
210 static void ui_draw_string(UiTerm *tui, int x, int y, const char *str, UiTermWin *win, enum UiStyle style_id) {
211 debug("draw-string: [%d][%d]\n", y, x);
212 if (x < 0 || x >= tui->width || y < 0 || y >= tui->height)
213 return;
214 CellStyle style = tui->styles[(win ? win->id : 0)*UI_STYLE_MAX + style_id];
215 // FIXME: does not handle double width characters etc, share code with view.c?
216 Cell *cells = tui->cells + y * tui->width;
217 const size_t cell_size = sizeof(cells[0].data)-1;
218 for (const char *next = str; *str && x < tui->width; str = next) {
219 do next++; while (!ISUTF8(*next));
220 size_t len = next - str;
221 if (!len)
222 break;
223 len = MIN(len, cell_size);
224 strncpy(cells[x].data, str, len);
225 cells[x].data[len] = '\0';
226 cells[x].style = style;
227 x++;
231 static void ui_window_draw(UiWin *w) {
232 UiTermWin *win = (UiTermWin*)w;
233 UiTerm *ui = win->ui;
234 View *view = win->win->view;
235 int width = win->width, height = win->height;
236 const Line *line = view_lines_first(view);
237 bool status = win->options & UI_OPTION_STATUSBAR;
238 bool nu = win->options & UI_OPTION_LINE_NUMBERS_ABSOLUTE;
239 bool rnu = win->options & UI_OPTION_LINE_NUMBERS_RELATIVE;
240 bool sidebar = nu || rnu;
241 int sidebar_width = sidebar ? snprintf(NULL, 0, "%zd ", line->lineno + height - 2) : 0;
242 if (sidebar_width != win->sidebar_width) {
243 view_resize(view, width - sidebar_width, status ? height - 1 : height);
244 win->sidebar_width = sidebar_width;
246 vis_window_draw(win->win);
247 line = view_lines_first(view);
248 size_t prev_lineno = 0;
249 Selection *sel = view_selections_primary_get(view);
250 const Line *cursor_line = view_cursors_line_get(sel);
251 size_t cursor_lineno = cursor_line->lineno;
252 char buf[(sizeof(size_t) * CHAR_BIT + 2) / 3 + 1 + 1];
253 int x = win->x, y = win->y;
254 int view_width = view_width_get(view);
255 Cell *cells = ui->cells + y * ui->width;
256 if (x + sidebar_width + view_width > ui->width)
257 view_width = ui->width - x - sidebar_width;
258 for (const Line *l = line; l; l = l->next, y++) {
259 if (sidebar) {
260 if (!l->lineno || !l->len || l->lineno == prev_lineno) {
261 memset(buf, ' ', sizeof(buf));
262 buf[sidebar_width] = '\0';
263 } else {
264 size_t number = l->lineno;
265 if (rnu) {
266 number = (win->options & UI_OPTION_LARGE_FILE) ? 0 : l->lineno;
267 if (l->lineno > cursor_lineno)
268 number = l->lineno - cursor_lineno;
269 else if (l->lineno < cursor_lineno)
270 number = cursor_lineno - l->lineno;
272 snprintf(buf, sizeof buf, "%*zu ", sidebar_width-1, number);
274 ui_draw_string(ui, x, y, buf, win,
275 (l->lineno == cursor_lineno) ? UI_STYLE_LINENUMBER_CURSOR : UI_STYLE_LINENUMBER);
276 prev_lineno = l->lineno;
278 debug("draw-window: [%d][%d] ... cells[%d][%d]\n", y, x+sidebar_width, y, view_width);
279 memcpy(&cells[x+sidebar_width], l->cells, sizeof(Cell) * view_width);
280 cells += ui->width;
284 static CellStyle ui_window_style_get(UiWin *w, enum UiStyle style) {
285 UiTermWin *win = (UiTermWin*)w;
286 UiTerm *tui = win->ui;
287 return tui->styles[win->id * UI_STYLE_MAX + style];
290 static void ui_window_status(UiWin *w, const char *status) {
291 UiTermWin *win = (UiTermWin*)w;
292 if (!(win->options & UI_OPTION_STATUSBAR))
293 return;
294 UiTerm *ui = win->ui;
295 enum UiStyle style = ui->selwin == win ? UI_STYLE_STATUS_FOCUSED : UI_STYLE_STATUS;
296 ui_draw_string(ui, win->x, win->y + win->height - 1, status, win, style);
299 static void ui_arrange(Ui *ui, enum UiLayout layout) {
300 debug("ui-arrange\n");
301 UiTerm *tui = (UiTerm*)ui;
302 tui->layout = layout;
303 int n = 0, m = !!tui->info[0], x = 0, y = 0;
304 for (UiTermWin *win = tui->windows; win; win = win->next) {
305 if (win->options & UI_OPTION_ONELINE)
306 m++;
307 else
308 n++;
310 int max_height = tui->height - m;
311 int width = (tui->width / MAX(1, n)) - 1;
312 int height = max_height / MAX(1, n);
313 for (UiTermWin *win = tui->windows; win; win = win->next) {
314 if (win->options & UI_OPTION_ONELINE)
315 continue;
316 n--;
317 if (layout == UI_LAYOUT_HORIZONTAL) {
318 int h = n ? height : max_height - y;
319 ui_window_resize(win, tui->width, h);
320 ui_window_move(win, x, y);
321 y += h;
322 } else {
323 int w = n ? width : tui->width - x;
324 ui_window_resize(win, w, max_height);
325 ui_window_move(win, x, y);
326 x += w;
327 if (n) {
328 Cell *cells = tui->cells;
329 for (int i = 0; i < max_height; i++) {
330 strcpy(cells[x].data,"│");
331 cells[x].style = tui->styles[UI_STYLE_SEPARATOR];
332 cells += tui->width;
334 x++;
339 if (layout == UI_LAYOUT_VERTICAL)
340 y = max_height;
342 for (UiTermWin *win = tui->windows; win; win = win->next) {
343 if (!(win->options & UI_OPTION_ONELINE))
344 continue;
345 ui_window_resize(win, tui->width, 1);
346 ui_window_move(win, 0, y++);
350 static void ui_draw(Ui *ui) {
351 debug("ui-draw\n");
352 UiTerm *tui = (UiTerm*)ui;
353 ui_arrange(ui, tui->layout);
354 for (UiTermWin *win = tui->windows; win; win = win->next)
355 ui_window_draw((UiWin*)win);
356 if (tui->info[0])
357 ui_draw_string(tui, 0, tui->height-1, tui->info, NULL, UI_STYLE_INFO);
358 ui_term_backend_blit(tui);
361 static void ui_redraw(Ui *ui) {
362 UiTerm *tui = (UiTerm*)ui;
363 ui_term_backend_clear(tui);
364 for (UiTermWin *win = tui->windows; win; win = win->next)
365 view_invalidate(win->win->view);
368 static void ui_resize(Ui *ui) {
369 UiTerm *tui = (UiTerm*)ui;
370 struct winsize ws;
371 int width = 80, height = 24;
373 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) != -1) {
374 if (ws.ws_col > 0)
375 width = ws.ws_col;
376 if (ws.ws_row > 0)
377 height = ws.ws_row;
380 width = MIN(width, MAX_WIDTH);
381 height = MIN(height, MAX_HEIGHT);
382 if (!ui_term_backend_resize(tui, width, height))
383 return;
385 size_t size = width*height*sizeof(Cell);
386 if (size > tui->cells_size) {
387 Cell *cells = realloc(tui->cells, size);
388 if (!cells)
389 return;
390 memset((char*)cells+tui->cells_size, 0, size - tui->cells_size);
391 tui->cells_size = size;
392 tui->cells = cells;
394 tui->width = width;
395 tui->height = height;
398 static void ui_window_free(UiWin *w) {
399 UiTermWin *win = (UiTermWin*)w;
400 if (!win)
401 return;
402 UiTerm *tui = win->ui;
403 if (win->prev)
404 win->prev->next = win->next;
405 if (win->next)
406 win->next->prev = win->prev;
407 if (tui->windows == win)
408 tui->windows = win->next;
409 if (tui->selwin == win)
410 tui->selwin = NULL;
411 win->next = win->prev = NULL;
412 tui->ids &= ~(1UL << win->id);
413 free(win);
416 static void ui_window_focus(UiWin *w) {
417 UiTermWin *new = (UiTermWin*)w;
418 UiTermWin *old = new->ui->selwin;
419 if (new->options & UI_OPTION_STATUSBAR)
420 new->ui->selwin = new;
421 if (old)
422 view_invalidate(old->win->view);
423 view_invalidate(new->win->view);
426 static void ui_window_options_set(UiWin *w, enum UiOption options) {
427 UiTermWin *win = (UiTermWin*)w;
428 win->options = options;
429 if (options & UI_OPTION_ONELINE) {
430 /* move the new window to the end of the list */
431 UiTerm *tui = win->ui;
432 UiTermWin *last = tui->windows;
433 while (last->next)
434 last = last->next;
435 if (last != win) {
436 if (win->prev)
437 win->prev->next = win->next;
438 if (win->next)
439 win->next->prev = win->prev;
440 if (tui->windows == win)
441 tui->windows = win->next;
442 last->next = win;
443 win->prev = last;
444 win->next = NULL;
447 ui_draw((Ui*)win->ui);
450 static enum UiOption ui_window_options_get(UiWin *win) {
451 return ((UiTermWin*)win)->options;
454 static int ui_window_width(UiWin *win) {
455 return ((UiTermWin*)win)->width;
458 static int ui_window_height(UiWin *win) {
459 return ((UiTermWin*)win)->height;
462 static void ui_window_swap(UiWin *aw, UiWin *bw) {
463 UiTermWin *a = (UiTermWin*)aw;
464 UiTermWin *b = (UiTermWin*)bw;
465 if (a == b || !a || !b)
466 return;
467 UiTerm *tui = a->ui;
468 UiTermWin *tmp = a->next;
469 a->next = b->next;
470 b->next = tmp;
471 if (a->next)
472 a->next->prev = a;
473 if (b->next)
474 b->next->prev = b;
475 tmp = a->prev;
476 a->prev = b->prev;
477 b->prev = tmp;
478 if (a->prev)
479 a->prev->next = a;
480 if (b->prev)
481 b->prev->next = b;
482 if (tui->windows == a)
483 tui->windows = b;
484 else if (tui->windows == b)
485 tui->windows = a;
486 if (tui->selwin == a)
487 ui_window_focus(bw);
488 else if (tui->selwin == b)
489 ui_window_focus(aw);
492 static UiWin *ui_window_new(Ui *ui, Win *w, enum UiOption options) {
493 UiTerm *tui = (UiTerm*)ui;
494 /* get rightmost zero bit, i.e. highest available id */
495 size_t bit = ~tui->ids & (tui->ids + 1);
496 size_t id = 0;
497 for (size_t tmp = bit; tmp >>= 1; id++);
498 if (id >= sizeof(size_t) * 8)
499 return NULL;
500 size_t styles_size = (id + 1) * UI_STYLE_MAX * sizeof(CellStyle);
501 if (styles_size > tui->styles_size) {
502 CellStyle *styles = realloc(tui->styles, styles_size);
503 if (!styles)
504 return NULL;
505 tui->styles = styles;
506 tui->styles_size = styles_size;
508 UiTermWin *win = calloc(1, sizeof(UiTermWin));
509 if (!win)
510 return NULL;
512 win->uiwin = (UiWin) {
513 .style_get = ui_window_style_get,
514 .status = ui_window_status,
515 .options_set = ui_window_options_set,
516 .options_get = ui_window_options_get,
517 .style_define = ui_style_define,
518 .window_width = ui_window_width,
519 .window_height = ui_window_height,
522 tui->ids |= bit;
523 win->id = id;
524 win->ui = tui;
525 win->win = w;
527 CellStyle *styles = &tui->styles[win->id * UI_STYLE_MAX];
528 for (int i = 0; i < UI_STYLE_MAX; i++) {
529 styles[i] = (CellStyle) {
530 .fg = CELL_COLOR_DEFAULT,
531 .bg = CELL_COLOR_DEFAULT,
532 .attr = CELL_ATTR_NORMAL,
536 styles[UI_STYLE_CURSOR].attr |= CELL_ATTR_REVERSE;
537 styles[UI_STYLE_CURSOR_PRIMARY].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BLINK;
538 styles[UI_STYLE_SELECTION].attr |= CELL_ATTR_REVERSE;
539 styles[UI_STYLE_COLOR_COLUMN].attr |= CELL_ATTR_REVERSE;
540 styles[UI_STYLE_STATUS].attr |= CELL_ATTR_REVERSE;
541 styles[UI_STYLE_STATUS_FOCUSED].attr |= CELL_ATTR_REVERSE|CELL_ATTR_BOLD;
542 styles[UI_STYLE_INFO].attr |= CELL_ATTR_BOLD;
543 view_ui(w->view, &win->uiwin);
545 if (tui->windows)
546 tui->windows->prev = win;
547 win->next = tui->windows;
548 tui->windows = win;
550 if (text_size(w->file->text) > UI_LARGE_FILE_SIZE) {
551 options |= UI_OPTION_LARGE_FILE;
552 options &= ~UI_OPTION_LINE_NUMBERS_ABSOLUTE;
555 ui_window_options_set((UiWin*)win, options);
557 return &win->uiwin;
560 static void ui_info(Ui *ui, const char *msg, va_list ap) {
561 UiTerm *tui = (UiTerm*)ui;
562 ui_draw_line(tui, 0, tui->height-1, ' ', UI_STYLE_INFO);
563 vsnprintf(tui->info, sizeof(tui->info), msg, ap);
566 static void ui_info_hide(Ui *ui) {
567 UiTerm *tui = (UiTerm*)ui;
568 if (tui->info[0])
569 tui->info[0] = '\0';
572 static TermKey *ui_termkey_new(int fd) {
573 TermKey *termkey = termkey_new(fd, UI_TERMKEY_FLAGS);
574 if (termkey)
575 termkey_set_canonflags(termkey, TERMKEY_CANON_DELBS);
576 return termkey;
579 static TermKey *ui_termkey_reopen(Ui *ui, int fd) {
580 int tty = open("/dev/tty", O_RDWR);
581 if (tty == -1)
582 return NULL;
583 if (tty != fd && dup2(tty, fd) == -1) {
584 close(tty);
585 return NULL;
587 close(tty);
588 return ui_termkey_new(fd);
591 static TermKey *ui_termkey_get(Ui *ui) {
592 UiTerm *tui = (UiTerm*)ui;
593 return tui->termkey;
596 static void ui_suspend(Ui *ui) {
597 UiTerm *tui = (UiTerm*)ui;
598 ui_term_backend_suspend(tui);
599 kill(0, SIGTSTP);
602 static void ui_resume(Ui *ui) {
603 UiTerm *tui = (UiTerm*)ui;
604 ui_term_backend_resume(tui);
607 static bool ui_getkey(Ui *ui, TermKeyKey *key) {
608 UiTerm *tui = (UiTerm*)ui;
609 TermKeyResult ret = termkey_getkey(tui->termkey, key);
611 if (ret == TERMKEY_RES_EOF) {
612 termkey_destroy(tui->termkey);
613 errno = 0;
614 if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)))
615 ui_die_msg(ui, "Failed to re-open stdin as /dev/tty: %s\n", errno != 0 ? strerror(errno) : "");
616 return false;
619 if (ret == TERMKEY_RES_AGAIN) {
620 struct pollfd fd;
621 fd.fd = STDIN_FILENO;
622 fd.events = POLLIN;
623 if (poll(&fd, 1, termkey_get_waittime(tui->termkey)) == 0)
624 ret = termkey_getkey_force(tui->termkey, key);
627 return ret == TERMKEY_RES_KEY;
630 static void ui_terminal_save(Ui *ui) {
631 UiTerm *tui = (UiTerm*)ui;
632 ui_term_backend_save(tui);
633 termkey_stop(tui->termkey);
636 static void ui_terminal_restore(Ui *ui) {
637 UiTerm *tui = (UiTerm*)ui;
638 termkey_start(tui->termkey);
639 ui_term_backend_restore(tui);
642 static bool ui_init(Ui *ui, Vis *vis) {
643 UiTerm *tui = (UiTerm*)ui;
644 tui->vis = vis;
646 setlocale(LC_CTYPE, "");
648 char *term = getenv("TERM");
649 if (!term) {
650 term = "xterm";
651 setenv("TERM", term, 1);
654 errno = 0;
655 if (!(tui->termkey = ui_termkey_new(STDIN_FILENO))) {
656 /* work around libtermkey bug which fails if stdin is /dev/null */
657 if (errno == EBADF) {
658 errno = 0;
659 if (!(tui->termkey = ui_termkey_reopen(ui, STDIN_FILENO)) && errno == ENXIO)
660 tui->termkey = termkey_new_abstract(term, UI_TERMKEY_FLAGS);
662 if (!tui->termkey)
663 goto err;
666 if (!ui_term_backend_init(tui, term))
667 goto err;
668 ui_resize(ui);
669 return true;
670 err:
671 ui_die_msg(ui, "Failed to start curses interface: %s\n", errno != 0 ? strerror(errno) : "");
672 return false;
675 Ui *ui_term_new(void) {
676 size_t styles_size = UI_STYLE_MAX * sizeof(CellStyle);
677 CellStyle *styles = calloc(1, styles_size);
678 if (!styles)
679 return NULL;
680 UiTerm *tui = ui_term_backend_new();
681 if (!tui) {
682 free(styles);
683 return NULL;
685 tui->styles_size = styles_size;
686 tui->styles = styles;
687 Ui *ui = (Ui*)tui;
688 *ui = (Ui) {
689 .init = ui_init,
690 .free = ui_term_free,
691 .termkey_get = ui_termkey_get,
692 .suspend = ui_suspend,
693 .resume = ui_resume,
694 .resize = ui_resize,
695 .window_new = ui_window_new,
696 .window_free = ui_window_free,
697 .window_focus = ui_window_focus,
698 .window_swap = ui_window_swap,
699 .draw = ui_draw,
700 .redraw = ui_redraw,
701 .arrange = ui_arrange,
702 .die = ui_die,
703 .info = ui_info,
704 .info_hide = ui_info_hide,
705 .getkey = ui_getkey,
706 .terminal_save = ui_terminal_save,
707 .terminal_restore = ui_terminal_restore,
708 .colors = ui_term_backend_colors,
711 return ui;
714 void ui_term_free(Ui *ui) {
715 UiTerm *tui = (UiTerm*)ui;
716 if (!tui)
717 return;
718 while (tui->windows)
719 ui_window_free((UiWin*)tui->windows);
720 ui_term_backend_free(tui);
721 if (tui->termkey)
722 termkey_destroy(tui->termkey);
723 free(tui->cells);
724 free(tui->styles);
725 free(tui);