KEY_EVENT might not be always defined
[dvtm.git] / vt.c
blob056e13278b5a914acd15b784269a8a7065e79ee5
1 /*
2 * Copyright © 2004 Bruno T. C. de Oliveira
3 * Copyright © 2006 Pierre Habouzit
4 * Copyright © 2008-2013 Marc André Tanner
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #define _GNU_SOURCE
20 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <langinfo.h>
24 #include <limits.h>
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stddef.h>
29 #include <string.h>
30 #include <sys/ioctl.h>
31 #include <sys/types.h>
32 #include <termios.h>
33 #include <wchar.h>
34 #if defined(__linux__) || defined(__CYGWIN__)
35 # include <pty.h>
36 #elif defined(__FreeBSD__)
37 # include <libutil.h>
38 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
39 # include <util.h>
40 #endif
41 #if defined(__CYGWIN__) || defined(_AIX)
42 # include <alloca.h>
43 #endif
45 #include "vt.h"
47 #ifdef _AIX
48 # include "forkpty-aix.c"
49 #endif
51 #ifndef NCURSES_ATTR_SHIFT
52 # define NCURSES_ATTR_SHIFT 8
53 #endif
55 #ifndef NCURSES_ACS
56 # ifdef PDCURSES
57 # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)])
58 # else /* BSD curses */
59 # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)])
60 # endif
61 #endif
63 #ifdef NCURSES_VERSION
64 # ifndef NCURSES_EXT_COLORS
65 # define NCURSES_EXT_COLORS 0
66 # endif
67 # if !NCURSES_EXT_COLORS
68 # define MAX_COLOR_PAIRS 256
69 # endif
70 #endif
71 #ifndef MAX_COLOR_PAIRS
72 # define MAX_COLOR_PAIRS COLOR_PAIRS
73 #endif
75 #define IS_CONTROL(ch) !((ch) & 0xffffff60UL)
76 #define MIN(x, y) ((x) < (y) ? (x) : (y))
77 #define sstrlen(str) (sizeof(str) - 1)
79 #define COPYMODE_ATTR A_REVERSE
80 static bool is_utf8, has_default_colors;
81 static short color_pairs_reserved, color_pairs_max, color_pair_current;
82 static short *color2palette, default_fg, default_bg;
83 static char vt_term[32] = "dvtm";
85 typedef struct {
86 wchar_t *buf;
87 wchar_t *cursor;
88 wchar_t *end;
89 wchar_t *display;
90 int size;
91 int width;
92 int cursor_pos;
93 mbstate_t ps;
94 enum {
95 CMDLINE_INACTIVE = 0,
96 CMDLINE_INIT,
97 CMDLINE_ACTIVE,
98 } state;
99 void *data;
100 void (*callback)(void *data);
101 char prefix;
102 } Cmdline;
104 typedef struct {
105 wchar_t text;
106 uint16_t attr;
107 short fg;
108 short bg;
109 } Cell;
111 typedef struct {
112 Cell *cells;
113 unsigned dirty:1;
114 } Row;
116 typedef struct {
117 Row *lines;
118 Row *curs_row;
119 Row *scroll_buf;
120 Row *scroll_top;
121 Row *scroll_bot;
122 bool *tabs;
123 int scroll_buf_size;
124 int scroll_buf_ptr;
125 int scroll_amount_above;
126 int scroll_amount_below;
127 int rows, cols, maxcols;
128 unsigned curattrs, savattrs;
129 int curs_col, curs_srow, curs_scol;
130 short curfg, curbg, savfg, savbg;
131 } Buffer;
133 struct Vt {
134 Buffer buffer_normal;
135 Buffer buffer_alternate;
136 Buffer *buffer;
137 unsigned defattrs;
138 short deffg, defbg;
139 int pty;
140 pid_t childpid;
142 /* flags */
143 unsigned seen_input:1;
144 unsigned insert:1;
145 unsigned escaped:1;
146 unsigned curshid:1;
147 unsigned curskeymode:1;
148 unsigned copymode:1;
149 unsigned copymode_selecting:1;
150 unsigned bell:1;
151 unsigned relposmode:1;
152 unsigned mousetrack:1;
153 unsigned graphmode:1;
154 unsigned savgraphmode:1;
155 bool charsets[2];
156 /* copymode */
157 int copymode_curs_srow, copymode_curs_scol;
158 Row *copymode_sel_start_row;
159 int copymode_sel_start_col;
160 int copymode_cmd_multiplier;
161 Cmdline *cmdline;
162 /* buffers and parsing state */
163 char rbuf[BUFSIZ];
164 char ebuf[BUFSIZ];
165 unsigned int rlen, elen;
167 /* xterm style window title */
168 char title[256];
170 vt_event_handler_t event_handler;
172 /* custom escape sequence handler */
173 vt_escseq_handler_t escseq_handler;
174 void *data;
177 static const char *keytable[KEY_MAX+1] = {
178 ['\n'] = "\r",
179 /* for the arrow keys the CSI / SS3 sequences are not stored here
180 * because they depend on the current cursor terminal mode
182 [KEY_UP] = "A",
183 [KEY_DOWN] = "B",
184 [KEY_RIGHT] = "C",
185 [KEY_LEFT] = "D",
186 #ifdef KEY_SUP
187 [KEY_SUP] = "\e[1;2A",
188 #endif
189 #ifdef KEY_SDOWN
190 [KEY_SDOWN] = "\e[1;2B",
191 #endif
192 [KEY_SRIGHT] = "\e[1;2C",
193 [KEY_SLEFT] = "\e[1;2D",
194 [KEY_BACKSPACE] = "\177",
195 [KEY_IC] = "\e[2~",
196 [KEY_DC] = "\e[3~",
197 [KEY_PPAGE] = "\e[5~",
198 [KEY_NPAGE] = "\e[6~",
199 [KEY_HOME] = "\e[7~",
200 [KEY_END] = "\e[8~",
201 [KEY_BTAB] = "\e[Z",
202 [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */
203 [KEY_F(1)] = "\e[11~",
204 [KEY_F(2)] = "\e[12~",
205 [KEY_F(3)] = "\e[13~",
206 [KEY_F(4)] = "\e[14~",
207 [KEY_F(5)] = "\e[15~",
208 [KEY_F(6)] = "\e[17~",
209 [KEY_F(7)] = "\e[18~",
210 [KEY_F(8)] = "\e[19~",
211 [KEY_F(9)] = "\e[20~",
212 [KEY_F(10)] = "\e[21~",
213 [KEY_F(11)] = "\e[23~",
214 [KEY_F(12)] = "\e[24~",
215 [KEY_F(13)] = "\e[23~",
216 [KEY_F(14)] = "\e[24~",
217 [KEY_F(15)] = "\e[25~",
218 [KEY_F(16)] = "\e[26~",
219 [KEY_F(17)] = "\e[28~",
220 [KEY_F(18)] = "\e[29~",
221 [KEY_F(19)] = "\e[31~",
222 [KEY_F(20)] = "\e[32~",
223 [KEY_F(21)] = "\e[33~",
224 [KEY_F(22)] = "\e[34~",
225 [KEY_RESIZE] = "",
226 #ifdef KEY_EVENT
227 [KEY_EVENT] = "",
228 #endif
231 static void puttab(Vt *t, int count);
232 static void process_nonprinting(Vt *t, wchar_t wc);
233 static void send_curs(Vt *t);
234 static void cmdline_hide_callback(void *t);
235 static void cmdline_free(Cmdline *c);
237 static int xwcwidth(wchar_t c) {
238 int w = wcwidth(c);
239 if (w == -1)
240 w = 1;
241 return w;
244 __attribute__ ((const))
245 static uint16_t build_attrs(unsigned curattrs)
247 return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
248 >> NCURSES_ATTR_SHIFT;
251 static void row_set(Row *row, int start, int len, Buffer *t)
253 Cell cell = {
254 .text = L'\0',
255 .attr = t ? build_attrs(t->curattrs) : 0,
256 .fg = t ? t->curfg : -1,
257 .bg = t ? t->curbg : -1,
260 for (int i = start; i < len + start; i++)
261 row->cells[i] = cell;
262 row->dirty = true;
265 static void row_roll(Row *start, Row *end, int count)
267 int n = end - start;
269 count %= n;
270 if (count < 0)
271 count += n;
273 if (count) {
274 Row *buf = alloca(count * sizeof(Row));
276 memcpy(buf, start, count * sizeof(Row));
277 memmove(start, start + count, (n - count) * sizeof(Row));
278 memcpy(end - count, buf, count * sizeof(Row));
279 for (Row *row = start; row < end; row++)
280 row->dirty = true;
284 static void clamp_cursor_to_bounds(Vt *t)
286 Buffer *b = t->buffer;
287 Row *lines = t->relposmode ? b->scroll_top : b->lines;
288 int rows = t->relposmode ? b->scroll_bot - b->scroll_top : b->rows;
290 if (b->curs_row < lines)
291 b->curs_row = lines;
292 if (b->curs_row >= lines + rows)
293 b->curs_row = lines + rows - 1;
294 if (b->curs_col < 0)
295 b->curs_col = 0;
296 if (b->curs_col >= b->cols)
297 b->curs_col = b->cols - 1;
300 static void save_curs(Vt *t)
302 Buffer *b = t->buffer;
303 b->curs_srow = b->curs_row - b->lines;
304 b->curs_scol = b->curs_col;
307 static void restore_curs(Vt *t)
309 Buffer *b = t->buffer;
310 b->curs_row = b->lines + b->curs_srow;
311 b->curs_col = b->curs_scol;
312 clamp_cursor_to_bounds(t);
315 static void save_attrs(Vt *t)
317 Buffer *b = t->buffer;
318 b->savattrs = b->curattrs;
319 b->savfg = b->curfg;
320 b->savbg = b->curbg;
321 t->savgraphmode = t->graphmode;
324 static void restore_attrs(Vt *t)
326 Buffer *b = t->buffer;
327 b->curattrs = b->savattrs;
328 b->curfg = b->savfg;
329 b->curbg = b->savbg;
330 t->graphmode = t->savgraphmode;
333 static void fill_scroll_buf(Buffer *t, int s)
335 /* work in screenfuls */
336 int ssz = t->scroll_bot - t->scroll_top;
337 if (s > ssz) {
338 fill_scroll_buf(t, ssz);
339 fill_scroll_buf(t, s - ssz);
340 return;
342 if (s < -ssz) {
343 fill_scroll_buf(t, -ssz);
344 fill_scroll_buf(t, s + ssz);
345 return;
348 t->scroll_amount_above += s;
349 if (t->scroll_amount_above >= t->scroll_buf_size)
350 t->scroll_amount_above = t->scroll_buf_size;
352 if (s > 0 && t->scroll_buf_size) {
353 for (int i = 0; i < s; i++) {
354 Row tmp = t->scroll_top[i];
355 t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr];
356 t->scroll_buf[t->scroll_buf_ptr] = tmp;
358 t->scroll_buf_ptr++;
359 if (t->scroll_buf_ptr == t->scroll_buf_size)
360 t->scroll_buf_ptr = 0;
363 row_roll(t->scroll_top, t->scroll_bot, s);
364 if (s < 0 && t->scroll_buf_size) {
365 for (int i = (-s) - 1; i >= 0; i--) {
366 t->scroll_buf_ptr--;
367 if (t->scroll_buf_ptr == -1)
368 t->scroll_buf_ptr = t->scroll_buf_size - 1;
370 Row tmp = t->scroll_top[i];
371 t->scroll_top[i] = t->scroll_buf[t->scroll_buf_ptr];
372 t->scroll_buf[t->scroll_buf_ptr] = tmp;
373 t->scroll_top[i].dirty = true;
378 static void cursor_line_down(Vt *t)
380 Buffer *b = t->buffer;
381 row_set(b->curs_row, b->cols, b->maxcols - b->cols, NULL);
382 b->curs_row++;
383 if (b->curs_row < b->scroll_bot)
384 return;
386 vt_noscroll(t);
388 b->curs_row = b->scroll_bot - 1;
389 fill_scroll_buf(b, 1);
390 row_set(b->curs_row, 0, b->cols, b);
393 static void new_escape_sequence(Vt *t)
395 t->escaped = true;
396 t->elen = 0;
397 t->ebuf[0] = '\0';
400 static void cancel_escape_sequence(Vt *t)
402 t->escaped = false;
403 t->elen = 0;
404 t->ebuf[0] = '\0';
407 static bool is_valid_csi_ender(int c)
409 return (c >= 'a' && c <= 'z')
410 || (c >= 'A' && c <= 'Z')
411 || (c == '@' || c == '`');
414 /* interprets a 'set attribute' (SGR) CSI escape sequence */
415 static void interpret_csi_sgr(Vt *t, int param[], int pcount)
417 Buffer *b = t->buffer;
418 if (pcount == 0) {
419 /* special case: reset attributes */
420 b->curattrs = A_NORMAL;
421 b->curfg = b->curbg = -1;
422 return;
425 for (int i = 0; i < pcount; i++) {
426 switch (param[i]) {
427 case 0:
428 b->curattrs = A_NORMAL;
429 b->curfg = b->curbg = -1;
430 break;
431 case 1:
432 b->curattrs |= A_BOLD;
433 break;
434 case 4:
435 b->curattrs |= A_UNDERLINE;
436 break;
437 case 5:
438 b->curattrs |= A_BLINK;
439 break;
440 case 7:
441 b->curattrs |= A_REVERSE;
442 break;
443 case 8:
444 b->curattrs |= A_INVIS;
445 break;
446 case 22:
447 b->curattrs &= ~A_BOLD;
448 break;
449 case 24:
450 b->curattrs &= ~A_UNDERLINE;
451 break;
452 case 25:
453 b->curattrs &= ~A_BLINK;
454 break;
455 case 27:
456 b->curattrs &= ~A_REVERSE;
457 break;
458 case 28:
459 b->curattrs &= ~A_INVIS;
460 break;
461 case 30 ... 37: /* fg */
462 b->curfg = param[i] - 30;
463 break;
464 case 38:
465 if ((i + 2) < pcount && param[i + 1] == 5) {
466 b->curfg = param[i + 2];
467 i += 2;
469 break;
470 case 39:
471 b->curfg = -1;
472 break;
473 case 40 ... 47: /* bg */
474 b->curbg = param[i] - 40;
475 break;
476 case 48:
477 if ((i + 2) < pcount && param[i + 1] == 5) {
478 b->curbg = param[i + 2];
479 i += 2;
481 break;
482 case 49:
483 b->curbg = -1;
484 break;
485 case 90 ... 97: /* hi fg */
486 b->curfg = param[i] - 82;
487 break;
488 case 100 ... 107: /* hi bg */
489 b->curbg = param[i] - 92;
490 break;
491 default:
492 break;
497 /* interprets an 'erase display' (ED) escape sequence */
498 static void interpret_csi_ed(Vt *t, int param[], int pcount)
500 Row *row, *start, *end;
501 Buffer *b = t->buffer;
503 save_attrs(t);
504 b->curattrs = A_NORMAL;
505 b->curfg = b->curbg = -1;
507 if (pcount && param[0] == 2) {
508 start = b->lines;
509 end = b->lines + b->rows;
510 } else if (pcount && param[0] == 1) {
511 start = b->lines;
512 end = b->curs_row;
513 row_set(b->curs_row, 0, b->curs_col + 1, b);
514 } else {
515 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
516 start = b->curs_row + 1;
517 end = b->lines + b->rows;
520 for (row = start; row < end; row++)
521 row_set(row, 0, b->cols, b);
523 restore_attrs(t);
526 /* interprets a 'move cursor' (CUP) escape sequence */
527 static void interpret_csi_cup(Vt *t, int param[], int pcount)
529 Buffer *b = t->buffer;
530 Row *lines = t->relposmode ? b->scroll_top : b->lines;
532 if (pcount == 0) {
533 b->curs_row = lines;
534 b->curs_col = 0;
535 } else if (pcount == 1) {
536 b->curs_row = lines + param[0] - 1;
537 b->curs_col = 0;
538 } else {
539 b->curs_row = lines + param[0] - 1;
540 b->curs_col = param[1] - 1;
543 clamp_cursor_to_bounds(t);
546 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
547 * CPL, CHA, HPR, VPA, VPR, HPA */
548 static void interpret_csi_c(Vt *t, char verb, int param[], int pcount)
550 Buffer *b = t->buffer;
551 int n = (pcount && param[0] > 0) ? param[0] : 1;
553 switch (verb) {
554 case 'A':
555 b->curs_row -= n;
556 break;
557 case 'B':
558 case 'e':
559 b->curs_row += n;
560 break;
561 case 'C':
562 case 'a':
563 b->curs_col += n;
564 break;
565 case 'D':
566 b->curs_col -= n;
567 break;
568 case 'E':
569 b->curs_row += n;
570 b->curs_col = 0;
571 break;
572 case 'F':
573 b->curs_row -= n;
574 b->curs_col = 0;
575 break;
576 case 'G':
577 case '`':
578 b->curs_col = param[0] - 1;
579 break;
580 case 'd':
581 b->curs_row = b->lines + param[0] - 1;
582 break;
585 clamp_cursor_to_bounds(t);
588 /* Interpret the 'erase line' escape sequence */
589 static void interpret_csi_el(Vt *t, int param[], int pcount)
591 Buffer *b = t->buffer;
592 switch (pcount ? param[0] : 0) {
593 case 1:
594 row_set(b->curs_row, 0, b->curs_col + 1, b);
595 break;
596 case 2:
597 row_set(b->curs_row, 0, b->cols, b);
598 break;
599 default:
600 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
601 break;
605 /* Interpret the 'insert blanks' sequence (ICH) */
606 static void interpret_csi_ich(Vt *t, int param[], int pcount)
608 Buffer *b = t->buffer;
609 Row *row = b->curs_row;
610 int n = (pcount && param[0] > 0) ? param[0] : 1;
612 if (b->curs_col + n > b->cols)
613 n = b->cols - b->curs_col;
615 for (int i = b->cols - 1; i >= b->curs_col + n; i--)
616 row->cells[i] = row->cells[i - n];
618 row_set(row, b->curs_col, n, b);
621 /* Interpret the 'delete chars' sequence (DCH) */
622 static void interpret_csi_dch(Vt *t, int param[], int pcount)
624 Buffer *b = t->buffer;
625 Row *row = b->curs_row;
626 int n = (pcount && param[0] > 0) ? param[0] : 1;
628 if (b->curs_col + n > b->cols)
629 n = b->cols - b->curs_col;
631 for (int i = b->curs_col; i < b->cols - n; i++)
632 row->cells[i] = row->cells[i + n];
634 row_set(row, b->cols - n, n, b);
637 /* Interpret an 'insert line' sequence (IL) */
638 static void interpret_csi_il(Vt *t, int param[], int pcount)
640 Buffer *b = t->buffer;
641 int n = (pcount && param[0] > 0) ? param[0] : 1;
643 if (b->curs_row + n >= b->scroll_bot) {
644 for (Row *row = b->curs_row; row < b->scroll_bot; row++)
645 row_set(row, 0, b->cols, b);
646 } else {
647 row_roll(b->curs_row, b->scroll_bot, -n);
648 for (Row *row = b->curs_row; row < b->curs_row + n; row++)
649 row_set(row, 0, b->cols, b);
653 /* Interpret a 'delete line' sequence (DL) */
654 static void interpret_csi_dl(Vt *t, int param[], int pcount)
656 Buffer *b = t->buffer;
657 int n = (pcount && param[0] > 0) ? param[0] : 1;
659 if (b->curs_row + n >= b->scroll_bot) {
660 for (Row *row = b->curs_row; row < b->scroll_bot; row++)
661 row_set(row, 0, b->cols, b);
662 } else {
663 row_roll(b->curs_row, b->scroll_bot, n);
664 for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++)
665 row_set(row, 0, b->cols, b);
669 /* Interpret an 'erase characters' (ECH) sequence */
670 static void interpret_csi_ech(Vt *t, int param[], int pcount)
672 Buffer *b = t->buffer;
673 int n = (pcount && param[0] > 0) ? param[0] : 1;
675 if (b->curs_col + n > b->cols)
676 n = b->cols - b->curs_col;
678 row_set(b->curs_row, b->curs_col, n, b);
681 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
682 static void interpret_csi_decstbm(Vt *t, int param[], int pcount)
684 Buffer *b = t->buffer;
685 int new_top, new_bot;
687 switch (pcount) {
688 case 0:
689 b->scroll_top = b->lines;
690 b->scroll_bot = b->lines + b->rows;
691 break;
692 case 2:
693 new_top = param[0] - 1;
694 new_bot = param[1];
696 /* clamp to bounds */
697 if (new_top < 0)
698 new_top = 0;
699 if (new_top >= b->rows)
700 new_top = b->rows - 1;
701 if (new_bot < 0)
702 new_bot = 0;
703 if (new_bot >= b->rows)
704 new_bot = b->rows;
706 /* check for range validity */
707 if (new_top < new_bot) {
708 b->scroll_top = b->lines + new_top;
709 b->scroll_bot = b->lines + new_bot;
711 break;
712 default:
713 return; /* malformed */
715 b->curs_row = b->scroll_top;
716 b->curs_col = 0;
719 static void interpret_csi_mode(Vt *t, int param[], int pcount, bool set)
721 for (int i = 0; i < pcount; i++) {
722 switch (param[i]) {
723 case 4: /* insert/replace mode */
724 t->insert = set;
725 break;
730 static void interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set)
732 for (int i = 0; i < pcount; i++) {
733 switch (param[i]) {
734 case 1: /* set application/normal cursor key mode (DECCKM) */
735 t->curskeymode = set;
736 break;
737 case 6: /* set origin to relative/absolute (DECOM) */
738 t->relposmode = set;
739 break;
740 case 25: /* make cursor visible/invisible (DECCM) */
741 t->curshid = !set;
742 break;
743 case 47: /* use alternate/normal screen buffer */
744 vt_copymode_leave(t);
745 t->buffer = set ? &t->buffer_alternate : &t->buffer_normal;
746 vt_dirty(t);
747 break;
748 case 1000: /* enable/disable normal mouse tracking */
749 t->mousetrack = set;
750 break;
755 static void interpret_csi(Vt *t)
757 static int csiparam[BUFSIZ];
758 Buffer *b = t->buffer;
759 int param_count = 0;
760 const char *p = t->ebuf + 1;
761 char verb = t->ebuf[t->elen - 1];
763 /* parse numeric parameters */
764 for (p += (t->ebuf[1] == '?'); *p; p++) {
765 if (IS_CONTROL(*p)) {
766 process_nonprinting(t, *p);
767 } else if (*p == ';') {
768 if (param_count >= (int)sizeof(csiparam))
769 return; /* too long! */
770 csiparam[param_count++] = 0;
771 } else if (isdigit((unsigned char)*p)) {
772 if (param_count == 0)
773 csiparam[param_count++] = 0;
774 csiparam[param_count - 1] *= 10;
775 csiparam[param_count - 1] += *p - '0';
779 if (t->ebuf[1] == '?') {
780 switch (verb) {
781 case 'h':
782 case 'l': /* private set/reset mode */
783 interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h');
784 break;
786 return;
789 /* delegate handling depending on command character (verb) */
790 switch (verb) {
791 case 'h':
792 case 'l': /* set/reset mode */
793 interpret_csi_mode(t, csiparam, param_count, verb == 'h');
794 break;
795 case 'm': /* set attribute */
796 interpret_csi_sgr(t, csiparam, param_count);
797 break;
798 case 'J': /* erase display */
799 interpret_csi_ed(t, csiparam, param_count);
800 break;
801 case 'H':
802 case 'f': /* move cursor */
803 interpret_csi_cup(t, csiparam, param_count);
804 break;
805 case 'A':
806 case 'B':
807 case 'C':
808 case 'D':
809 case 'E':
810 case 'F':
811 case 'G':
812 case 'e':
813 case 'a':
814 case 'd':
815 case '`': /* relative move */
816 interpret_csi_c(t, verb, csiparam, param_count);
817 break;
818 case 'K': /* erase line */
819 interpret_csi_el(t, csiparam, param_count);
820 break;
821 case '@': /* insert characters */
822 interpret_csi_ich(t, csiparam, param_count);
823 break;
824 case 'P': /* delete characters */
825 interpret_csi_dch(t, csiparam, param_count);
826 break;
827 case 'L': /* insert lines */
828 interpret_csi_il(t, csiparam, param_count);
829 break;
830 case 'M': /* delete lines */
831 interpret_csi_dl(t, csiparam, param_count);
832 break;
833 case 'X': /* erase chars */
834 interpret_csi_ech(t, csiparam, param_count);
835 break;
836 case 'S': /* SU: scroll up */
837 vt_scroll(t, param_count ? -csiparam[0] : -1);
838 break;
839 case 'T': /* SD: scroll down */
840 vt_scroll(t, param_count ? csiparam[0] : 1);
841 break;
842 case 'Z': /* CBT: cursor backward tabulation */
843 puttab(t, param_count ? -csiparam[0] : -1);
844 break;
845 case 'g': /* TBC: tabulation clear */
846 switch (csiparam[0]) {
847 case 0:
848 b->tabs[b->curs_col] = false;
849 break;
850 case 3:
851 memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols);
852 break;
854 case 'r': /* set scrolling region */
855 interpret_csi_decstbm(t, csiparam, param_count);
856 break;
857 case 's': /* save cursor location */
858 save_curs(t);
859 break;
860 case 'u': /* restore cursor location */
861 restore_curs(t);
862 break;
863 case 'n': /* query cursor location */
864 if (param_count == 1 && csiparam[0] == 6)
865 send_curs(t);
866 break;
867 default:
868 break;
872 /* Interpret an 'index' (IND) sequence */
873 static void interpret_csi_ind(Vt *t)
875 Buffer *b = t->buffer;
876 if (b->curs_row < b->lines + b->rows - 1)
877 b->curs_row++;
880 /* Interpret a 'reverse index' (RI) sequence */
881 static void interpret_csi_ri(Vt *t)
883 Buffer *b = t->buffer;
884 if (b->curs_row > b->scroll_top)
885 b->curs_row--;
886 else {
887 row_roll(b->scroll_top, b->scroll_bot, -1);
888 row_set(b->scroll_top, 0, b->cols, b);
892 /* Interpret a 'next line' (NEL) sequence */
893 static void interpret_csi_nel(Vt *t)
895 Buffer *b = t->buffer;
896 if (b->curs_row < b->lines + b->rows - 1) {
897 b->curs_row++;
898 b->curs_col = 0;
902 /* Interpret a 'select character set' (SCS) sequence */
903 static void interpret_csi_scs(Vt *t)
905 /* ESC ( sets G0, ESC ) sets G1 */
906 t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0');
907 t->graphmode = t->charsets[0];
910 /* Interpret xterm specific escape sequences */
911 static void interpret_esc_xterm(Vt *t)
913 /* ESC]n;dataBEL -- the ESC is not part of t->ebuf */
914 char *title = NULL;
916 switch (t->ebuf[1]) {
917 case '0':
918 case '2':
919 t->ebuf[t->elen - 1] = '\0';
920 if (t->elen > sstrlen("]n;\a"))
921 title = t->ebuf + sstrlen("]n;");
923 if (t->event_handler)
924 t->event_handler(t, VT_EVENT_TITLE, title);
928 static void try_interpret_escape_seq(Vt *t)
930 char lastchar = t->ebuf[t->elen - 1];
932 if (!*t->ebuf)
933 return;
935 if (t->escseq_handler) {
936 switch ((*(t->escseq_handler)) (t, t->ebuf)) {
937 case VT_ESCSEQ_HANDLER_OK:
938 cancel_escape_sequence(t);
939 return;
940 case VT_ESCSEQ_HANDLER_NOTYET:
941 if (t->elen + 1 >= sizeof(t->ebuf))
942 goto cancel;
943 return;
947 switch (*t->ebuf) {
948 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */
949 if (t->elen == 2) {
950 if (lastchar == '8') { /* DECALN */
951 interpret_csi_ed(t, (int []){ 2 }, 1);
952 goto handled;
954 goto cancel;
956 break;
957 case '(':
958 case ')':
959 if (t->elen == 2) {
960 interpret_csi_scs(t);
961 goto handled;
963 break;
964 case ']': /* xterm thing */
965 if (lastchar == '\a' ||
966 (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) {
967 interpret_esc_xterm(t);
968 goto handled;
970 break;
971 case '[':
972 if (is_valid_csi_ender(lastchar)) {
973 interpret_csi(t);
974 goto handled;
976 break;
977 case '7': /* DECSC: save cursor and attributes */
978 save_attrs(t);
979 save_curs(t);
980 goto handled;
981 case '8': /* DECRC: restore cursor and attributes */
982 restore_attrs(t);
983 restore_curs(t);
984 goto handled;
985 case 'D': /* IND: index */
986 interpret_csi_ind(t);
987 goto handled;
988 case 'M': /* RI: reverse index */
989 interpret_csi_ri(t);
990 goto handled;
991 case 'E': /* NEL: next line */
992 interpret_csi_nel(t);
993 goto handled;
994 case 'H': /* HTS: horizontal tab set */
995 t->buffer->tabs[t->buffer->curs_col] = true;
996 goto handled;
997 default:
998 goto cancel;
1001 if (t->elen + 1 >= sizeof(t->ebuf)) {
1002 cancel:
1003 #ifndef NDEBUG
1004 fprintf(stderr, "cancelled: \\033");
1005 for (unsigned int i = 0; i < t->elen; i++) {
1006 if (isprint(t->ebuf[i])) {
1007 fputc(t->ebuf[i], stderr);
1008 } else {
1009 fprintf(stderr, "\\%03o", t->ebuf[i]);
1012 fputc('\n', stderr);
1013 #endif
1014 handled:
1015 cancel_escape_sequence(t);
1019 static void puttab(Vt *t, int count)
1021 Buffer *b = t->buffer;
1022 int direction = count >= 0 ? 1 : -1;
1023 int col = b->curs_col + direction;
1024 while (count) {
1025 if (col < 0) {
1026 b->curs_col = 0;
1027 break;
1029 if (col >= b->cols) {
1030 b->curs_col = b->cols - 1;
1031 break;
1033 if (b->tabs[col]) {
1034 b->curs_col = col;
1035 count -= direction;
1037 col += direction;
1041 static void process_nonprinting(Vt *t, wchar_t wc)
1043 Buffer *b = t->buffer;
1044 switch (wc) {
1045 case '\e': /* ESC */
1046 new_escape_sequence(t);
1047 break;
1048 case '\a': /* BEL */
1049 if (t->bell)
1050 beep();
1051 break;
1052 case '\b': /* BS */
1053 if (b->curs_col > 0)
1054 b->curs_col--;
1055 break;
1056 case '\t': /* HT */
1057 puttab(t, 1);
1058 break;
1059 case '\r': /* CR */
1060 b->curs_col = 0;
1061 break;
1062 case '\v': /* VT */
1063 case '\f': /* FF */
1064 case '\n': /* LF */
1065 cursor_line_down(t);
1066 break;
1067 case '\016': /* SO: shift out, invoke the G1 character set */
1068 t->graphmode = t->charsets[1];
1069 break;
1070 case '\017': /* SI: shift in, invoke the G0 character set */
1071 t->graphmode = t->charsets[0];
1072 break;
1076 static void is_utf8_locale(void)
1078 const char *cset = nl_langinfo(CODESET);
1079 if (!cset)
1080 cset = "ANSI_X3.4-1968";
1081 is_utf8 = !strcmp(cset, "UTF-8");
1084 static wchar_t get_vt100_graphic(char c)
1086 static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~";
1089 * 5f-7e standard vt100
1090 * 40-5e rxvt extension for extra curses acs chars
1092 static uint16_t const vt100_utf8[62] = {
1093 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
1094 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f
1095 0, 0, 0, 0, 0, 0, 0, 0, // 50-57
1096 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f
1097 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
1098 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
1099 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
1100 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e
1103 if (is_utf8)
1104 return vt100_utf8[c - 0x41];
1105 else if (strchr(vt100_acs, c))
1106 return NCURSES_ACS(c);
1107 return '\0';
1110 static void put_wc(Vt *t, wchar_t wc)
1112 int width = 0;
1114 if (!t->seen_input) {
1115 t->seen_input = 1;
1116 kill(-t->childpid, SIGWINCH);
1119 if (t->escaped) {
1120 if (t->elen + 1 < sizeof(t->ebuf)) {
1121 t->ebuf[t->elen] = wc;
1122 t->ebuf[++t->elen] = '\0';
1123 try_interpret_escape_seq(t);
1124 } else {
1125 cancel_escape_sequence(t);
1127 } else if (IS_CONTROL(wc)) {
1128 process_nonprinting(t, wc);
1129 } else {
1130 if (t->graphmode) {
1131 if (wc >= 0x41 && wc <= 0x7e) {
1132 wchar_t gc = get_vt100_graphic(wc);
1133 if (gc)
1134 wc = gc;
1136 width = 1;
1137 } else if ((width = wcwidth(wc)) < 1) {
1138 width = 1;
1140 Buffer *b = t->buffer;
1141 Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg };
1142 if (width == 2 && b->curs_col == b->cols - 1) {
1143 b->curs_row->cells[b->curs_col++] = blank_cell;
1144 b->curs_row->dirty = true;
1147 if (b->curs_col >= b->cols) {
1148 b->curs_col = 0;
1149 cursor_line_down(t);
1152 if (t->insert) {
1153 Cell *src = b->curs_row->cells + b->curs_col;
1154 Cell *dest = src + width;
1155 size_t len = b->cols - b->curs_col - width;
1156 memmove(dest, src, len * sizeof *dest);
1159 b->curs_row->cells[b->curs_col] = blank_cell;
1160 b->curs_row->cells[b->curs_col++].text = wc;
1161 b->curs_row->dirty = true;
1162 if (width == 2)
1163 b->curs_row->cells[b->curs_col++] = blank_cell;
1167 int vt_process(Vt *t)
1169 int res;
1170 unsigned int pos = 0;
1171 mbstate_t ps;
1172 memset(&ps, 0, sizeof(ps));
1174 if (t->pty < 0) {
1175 errno = EINVAL;
1176 return -1;
1179 res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen);
1180 if (res < 0)
1181 return -1;
1183 t->rlen += res;
1184 while (pos < t->rlen) {
1185 wchar_t wc;
1186 ssize_t len;
1188 len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps);
1189 if (len == -2) {
1190 t->rlen -= pos;
1191 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1192 return 0;
1195 if (len == -1) {
1196 len = 1;
1197 wc = t->rbuf[pos];
1200 pos += len ? len : 1;
1201 put_wc(t, wc);
1204 t->rlen -= pos;
1205 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1206 return 0;
1209 void vt_set_default_colors(Vt *t, unsigned attrs, short fg, short bg)
1211 t->defattrs = attrs;
1212 t->deffg = fg;
1213 t->defbg = bg;
1216 static void buffer_free(Buffer *t)
1218 for (int i = 0; i < t->rows; i++)
1219 free(t->lines[i].cells);
1220 free(t->lines);
1221 for (int i = 0; i < t->scroll_buf_size; i++)
1222 free(t->scroll_buf[i].cells);
1223 free(t->scroll_buf);
1224 free(t->tabs);
1227 static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_size)
1229 Row *lines, *scroll_buf;
1230 t->lines = lines = calloc(rows, sizeof(Row));
1231 if (!lines)
1232 return false;
1233 t->curattrs = A_NORMAL; /* white text over black background */
1234 t->curfg = t->curbg = -1;
1235 for (Row *row = lines, *end = lines + rows; row < end; row++) {
1236 row->cells = malloc(cols * sizeof(Cell));
1237 if (!row->cells) {
1238 t->rows = row - lines;
1239 goto fail;
1241 row_set(row, 0, cols, NULL);
1243 t->rows = rows;
1244 if (scroll_buf_size < 0)
1245 scroll_buf_size = 0;
1246 t->scroll_buf = scroll_buf = calloc(scroll_buf_size, sizeof(Row));
1247 if (!scroll_buf && scroll_buf_size)
1248 goto fail;
1249 for (Row *row = scroll_buf, *end = scroll_buf + scroll_buf_size; row < end; row++) {
1250 row->cells = calloc(cols, sizeof(Cell));
1251 if (!row->cells) {
1252 t->scroll_buf_size = row - scroll_buf;
1253 goto fail;
1256 t->tabs = calloc(cols, sizeof(*t->tabs));
1257 if (!t->tabs)
1258 goto fail;
1259 for (int col = 8; col < cols; col += 8)
1260 t->tabs[col] = true;
1261 t->curs_row = lines;
1262 t->curs_col = 0;
1263 /* initial scrolling area is the whole window */
1264 t->scroll_top = lines;
1265 t->scroll_bot = lines + rows;
1266 t->scroll_buf_size = scroll_buf_size;
1267 t->maxcols = t->cols = cols;
1268 return true;
1270 fail:
1271 buffer_free(t);
1272 return false;
1275 Vt *vt_create(int rows, int cols, int scroll_buf_size)
1277 Vt *t;
1279 if (rows <= 0 || cols <= 0)
1280 return NULL;
1282 t = calloc(1, sizeof(Vt));
1283 if (!t)
1284 return NULL;
1286 t->pty = -1;
1287 t->deffg = t->defbg = -1;
1288 if (!buffer_init(&t->buffer_normal, rows, cols, scroll_buf_size) ||
1289 !buffer_init(&t->buffer_alternate, rows, cols, 0)) {
1290 free(t);
1291 return NULL;
1293 t->buffer = &t->buffer_normal;
1294 t->copymode_cmd_multiplier = 0;
1295 return t;
1298 static void buffer_resize(Buffer *t, int rows, int cols)
1300 Row *lines = t->lines;
1302 if (t->rows != rows) {
1303 if (t->curs_row > lines + rows) {
1304 /* scroll up instead of simply chopping off bottom */
1305 fill_scroll_buf(t, (t->curs_row - t->lines) - rows + 1);
1307 while (t->rows > rows) {
1308 free(lines[t->rows - 1].cells);
1309 t->rows--;
1312 lines = realloc(lines, sizeof(Row) * rows);
1315 if (t->maxcols < cols) {
1316 for (int row = 0; row < t->rows; row++) {
1317 lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols);
1318 if (t->cols < cols)
1319 row_set(lines + row, t->cols, cols - t->cols, NULL);
1320 lines[row].dirty = true;
1322 Row *sbuf = t->scroll_buf;
1323 for (int row = 0; row < t->scroll_buf_size; row++) {
1324 sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols);
1325 if (t->cols < cols)
1326 row_set(sbuf + row, t->cols, cols - t->cols, NULL);
1328 t->tabs = realloc(t->tabs, sizeof(*t->tabs) * cols);
1329 for (int col = t->cols; col < cols; col++)
1330 t->tabs[col] = !(col & 7);
1331 t->maxcols = cols;
1332 t->cols = cols;
1333 } else if (t->cols != cols) {
1334 for (int row = 0; row < t->rows; row++)
1335 lines[row].dirty = true;
1336 t->cols = cols;
1339 int deltarows = 0;
1340 if (t->rows < rows) {
1341 while (t->rows < rows) {
1342 lines[t->rows].cells = calloc(t->maxcols, sizeof(Cell));
1343 row_set(lines + t->rows, 0, t->maxcols, t);
1344 t->rows++;
1347 /* prepare for backfill */
1348 if (t->curs_row >= t->scroll_bot - 1) {
1349 deltarows = t->lines + rows - t->curs_row - 1;
1350 if (deltarows > t->scroll_amount_above)
1351 deltarows = t->scroll_amount_above;
1355 t->curs_row += lines - t->lines;
1356 t->scroll_top = lines;
1357 t->scroll_bot = lines + rows;
1358 t->lines = lines;
1360 /* perform backfill */
1361 if (deltarows > 0) {
1362 fill_scroll_buf(t, -deltarows);
1363 t->curs_row += deltarows;
1367 void vt_resize(Vt *t, int rows, int cols)
1369 struct winsize ws = { .ws_row = rows, .ws_col = cols };
1371 if (rows <= 0 || cols <= 0)
1372 return;
1374 vt_noscroll(t);
1375 if (t->copymode)
1376 vt_copymode_leave(t);
1377 buffer_resize(&t->buffer_normal, rows, cols);
1378 buffer_resize(&t->buffer_alternate, rows, cols);
1379 clamp_cursor_to_bounds(t);
1380 ioctl(t->pty, TIOCSWINSZ, &ws);
1381 kill(-t->childpid, SIGWINCH);
1384 void vt_destroy(Vt *t)
1386 if (!t)
1387 return;
1388 buffer_free(&t->buffer_normal);
1389 buffer_free(&t->buffer_alternate);
1390 cmdline_free(t->cmdline);
1391 close(t->pty);
1392 free(t);
1395 void vt_dirty(Vt *t)
1397 Buffer *b = t->buffer;
1398 for (Row *row = b->lines, *end = row + b->rows; row < end; row++)
1399 row->dirty = true;
1402 static void copymode_get_selection_boundry(Vt *t, Row **start_row, int *start_col, Row **end_row, int *end_col, bool clip) {
1403 Buffer *b = t->buffer;
1404 if (t->copymode_sel_start_row >= b->lines && t->copymode_sel_start_row < b->lines + b->rows) {
1405 /* within the current page */
1406 if (b->curs_row >= t->copymode_sel_start_row) {
1407 *start_row = t->copymode_sel_start_row;
1408 *end_row = b->curs_row;
1409 *start_col = t->copymode_sel_start_col;
1410 *end_col = b->curs_col;
1411 } else {
1412 *start_row = b->curs_row;
1413 *end_row = t->copymode_sel_start_row;
1414 *start_col = b->curs_col;
1415 *end_col = t->copymode_sel_start_col;
1417 if (b->curs_col < *start_col && *start_row == *end_row) {
1418 *start_col = b->curs_col;
1419 *end_col = t->copymode_sel_start_col;
1421 } else {
1422 /* part of the scrollback buffer is also selected */
1423 if (t->copymode_sel_start_row < b->lines) {
1424 /* above the current page */
1425 if (clip) {
1426 *start_row = b->lines;
1427 *start_col = 0;
1428 } else {
1429 int copied_lines = b->lines - t->copymode_sel_start_row;
1430 *start_row = &b->scroll_buf
1431 [(b->scroll_buf_ptr - copied_lines + b->scroll_buf_size) % b->scroll_buf_size];
1432 *start_col = t->copymode_sel_start_col;
1434 *end_row = b->curs_row;
1435 *end_col = b->curs_col;
1436 } else {
1437 /* below the current page */
1438 *start_row = b->curs_row;
1439 *start_col = b->curs_col;
1440 if (clip) {
1441 *end_row = b->lines + b->rows;
1442 *end_col = b->cols - 1;
1443 } else {
1444 int copied_lines = t->copymode_sel_start_row -(b->lines + b->rows);
1445 *end_row = &b->scroll_buf
1446 [(b->scroll_buf_ptr + copied_lines) % b->scroll_buf_size];
1447 *end_col = t->copymode_sel_start_col;
1453 void vt_draw(Vt *t, WINDOW * win, int srow, int scol)
1455 Buffer *b = t->buffer;
1456 bool sel = false;
1457 Row *sel_row_start, *sel_row_end;
1458 int sel_col_start, sel_col_end;
1460 copymode_get_selection_boundry(t, &sel_row_start, &sel_col_start, &sel_row_end, &sel_col_end, true);
1462 for (int i = 0; i < b->rows; i++) {
1463 Row *row = b->lines + i;
1465 if (!row->dirty)
1466 continue;
1468 wmove(win, srow + i, scol);
1469 Cell *cell = NULL;
1470 for (int j = 0; j < b->cols; j++) {
1471 Cell *prev_cell = cell;
1472 cell = row->cells + j;
1473 if (!prev_cell || cell->attr != prev_cell->attr
1474 || cell->fg != prev_cell->fg
1475 || cell->bg != prev_cell->bg) {
1476 if (cell->attr == A_NORMAL)
1477 cell->attr = t->defattrs;
1478 if (cell->fg == -1)
1479 cell->fg = t->deffg;
1480 if (cell->bg == -1)
1481 cell->bg = t->defbg;
1482 wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT);
1483 wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL);
1486 if (t->copymode_selecting && ((row > sel_row_start && row < sel_row_end) ||
1487 (row == sel_row_start && j >= sel_col_start && (row != sel_row_end || j <= sel_col_end)) ||
1488 (row == sel_row_end && j <= sel_col_end && (row != sel_row_start || j >= sel_col_start)))) {
1489 wattrset(win, (attr_t) ((cell->attr << NCURSES_ATTR_SHIFT)|COPYMODE_ATTR));
1490 sel = true;
1491 } else if (sel) {
1492 wattrset(win, (attr_t) cell->attr << NCURSES_ATTR_SHIFT);
1493 wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL);
1494 sel = false;
1497 if (is_utf8 && cell->text >= 128) {
1498 char buf[MB_CUR_MAX + 1];
1499 size_t len = wcrtomb(buf, cell->text, NULL);
1500 if (len > 0) {
1501 waddnstr(win, buf, len);
1502 if (wcwidth(cell->text) > 1)
1503 j++;
1505 } else {
1506 waddch(win, cell->text > ' ' ? cell->text : ' ');
1510 int x, y;
1511 getyx(win, y, x);
1512 (void)y;
1513 if (x && x < b->cols - 1)
1514 whline(win, ' ', b->cols - x);
1516 row->dirty = false;
1519 wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col);
1521 if (t->cmdline && t->cmdline->state) {
1522 wattrset(win, t->defattrs << NCURSES_ATTR_SHIFT);
1523 mvwaddch(win, srow + b->rows - 1, 0, t->cmdline->prefix);
1524 whline(win, ' ', b->cols - 1);
1525 if (t->cmdline->state == CMDLINE_ACTIVE) {
1526 waddnwstr(win, t->cmdline->display, b->cols - 1);
1527 wmove(win, srow + b->rows - 1, 1 + t->cmdline->cursor_pos);
1528 } else
1529 wmove(win, srow + b->rows - 1, 1);
1533 void vt_scroll(Vt *t, int rows)
1535 Buffer *b = t->buffer;
1536 if (!b->scroll_buf_size)
1537 return;
1538 if (rows < 0) { /* scroll back */
1539 if (rows < -b->scroll_amount_above)
1540 rows = -b->scroll_amount_above;
1541 } else { /* scroll forward */
1542 if (rows > b->scroll_amount_below)
1543 rows = b->scroll_amount_below;
1545 fill_scroll_buf(b, rows);
1546 b->scroll_amount_below -= rows;
1547 if (t->copymode_selecting)
1548 t->copymode_sel_start_row -= rows;
1551 void vt_noscroll(Vt *t)
1553 int scroll_amount_below = t->buffer->scroll_amount_below;
1554 if (scroll_amount_below)
1555 vt_scroll(t, scroll_amount_below);
1558 void vt_bell(Vt *t, bool bell)
1560 t->bell = bell;
1563 void vt_togglebell(Vt *t)
1565 t->bell = !t->bell;
1568 pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *pty)
1570 struct winsize ws;
1571 pid_t pid;
1572 const char **envp = env;
1573 int fd, maxfd;
1575 ws.ws_row = t->buffer->rows;
1576 ws.ws_col = t->buffer->cols;
1577 ws.ws_xpixel = ws.ws_ypixel = 0;
1579 pid = forkpty(&t->pty, NULL, NULL, &ws);
1580 if (pid < 0)
1581 return -1;
1583 if (pid == 0) {
1584 setsid();
1586 maxfd = sysconf(_SC_OPEN_MAX);
1587 for (fd = 3; fd < maxfd; fd++)
1588 if (close(fd) == -1 && errno == EBADF)
1589 break;
1591 while (envp && envp[0]) {
1592 setenv(envp[0], envp[1], 1);
1593 envp += 2;
1595 setenv("TERM", vt_term, 1);
1596 if (cwd)
1597 chdir(cwd);
1598 execv(p, (char *const *)argv);
1599 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
1600 exit(1);
1603 if (pty)
1604 *pty = t->pty;
1605 return t->childpid = pid;
1608 int vt_getpty(Vt *t)
1610 return t->pty;
1613 int vt_write(Vt *t, const char *buf, int len)
1615 int ret = len;
1617 while (len > 0) {
1618 int res = write(t->pty, buf, len);
1619 if (res < 0) {
1620 if (errno != EAGAIN && errno != EINTR)
1621 return -1;
1622 else
1623 continue;
1625 buf += res;
1626 len -= res;
1629 return ret;
1632 static void send_curs(Vt *t)
1634 Buffer *b = t->buffer;
1635 char keyseq[16];
1636 snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col);
1637 vt_write(t, keyseq, strlen(keyseq));
1640 void vt_keypress(Vt *t, int keycode)
1642 vt_noscroll(t);
1644 if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) {
1645 switch (keycode) {
1646 case KEY_UP:
1647 case KEY_DOWN:
1648 case KEY_RIGHT:
1649 case KEY_LEFT: {
1650 char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] };
1651 vt_write(t, keyseq, sizeof keyseq);
1652 break;
1654 default:
1655 vt_write(t, keytable[keycode], strlen(keytable[keycode]));
1657 } else if (keycode <= UCHAR_MAX) {
1658 char c = keycode;
1659 vt_write(t, &c, 1);
1660 } else {
1661 #ifndef NDEBUG
1662 fprintf(stderr, "unhandled key %#o\n", keycode);
1663 #endif
1667 static Row *buffer_next_row(Buffer *t, Row *row, int direction)
1669 bool has_scroll_buf = t->scroll_buf_size > 0;
1670 Row *before_start_row, *before_end_row, *after_start_row, *after_end_row;
1671 Row *first_row = t->lines;
1672 Row *last_row = t->lines + t->rows - 1;
1674 if (has_scroll_buf) {
1675 before_end_row = &t->scroll_buf
1676 [(t->scroll_buf_ptr - 1 + t->scroll_buf_size) % t->scroll_buf_size];
1677 before_start_row = &t->scroll_buf
1678 [(t->scroll_buf_ptr - t->scroll_amount_above + t->scroll_buf_size) % t->scroll_buf_size];
1679 after_start_row = &t->scroll_buf[t->scroll_buf_ptr];
1680 after_end_row = &t->scroll_buf
1681 [(t->scroll_buf_ptr + t->scroll_amount_below - 1) % t->scroll_buf_size];
1684 if (direction > 0) {
1685 if (row >= first_row && row < last_row)
1686 return ++row;
1687 if (row == last_row) {
1688 if (has_scroll_buf) {
1689 if (t->scroll_amount_below)
1690 return after_start_row;
1691 else if (t->scroll_amount_above)
1692 return before_start_row;
1694 return first_row;
1696 if (row == before_end_row)
1697 return first_row;
1698 if (row == after_end_row)
1699 return t->scroll_amount_above ? before_start_row : first_row;
1700 if (row == &t->scroll_buf[t->scroll_buf_size - 1])
1701 return t->scroll_buf;
1702 return ++row;
1703 } else {
1704 if (row > first_row && row <= last_row)
1705 return --row;
1706 if (row == first_row) {
1707 if (has_scroll_buf) {
1708 if (t->scroll_amount_above)
1709 return before_end_row;
1710 else if (t->scroll_amount_below)
1711 return after_end_row;
1713 return last_row;
1715 if (row == before_start_row)
1716 return t->scroll_amount_below ? after_end_row : last_row;
1717 if (row == after_start_row)
1718 return last_row;
1719 if (row == t->scroll_buf)
1720 return &t->scroll_buf[t->scroll_buf_size - 1];
1721 return --row;
1725 static void row_show(Vt *t, Row *r)
1727 Buffer *b = t->buffer;
1728 int below = b->scroll_amount_below;
1729 int above = b->scroll_amount_above;
1730 int ptr = b->scroll_buf_ptr;
1731 int size = b->scroll_buf_size;
1732 int row = r - b->scroll_buf;
1733 int scroll = 0;
1735 if (b->lines <= r && r < b->lines + b->rows) {
1736 b->curs_row = r;
1737 return;
1740 if (!size)
1741 return;
1743 if (row < ptr) {
1744 if (row - ptr + size < below)
1745 scroll = row - ptr + size + 1;
1746 else if (ptr - row <= above)
1747 scroll = row - ptr;
1748 } else {
1749 if (row - ptr < below)
1750 scroll = row - ptr + 1;
1751 else if (ptr - row + size <= above)
1752 scroll = row - ptr - size;
1755 if (scroll) {
1756 vt_scroll(t, scroll);
1757 b->curs_row = b->lines + (scroll > 0 ? b->rows - 1 : 0);
1761 static void copymode_search(Vt *t, int direction)
1763 wchar_t *searchbuf = t->cmdline ? t->cmdline->buf : NULL;
1764 if (!searchbuf || *searchbuf == '\0')
1765 return;
1767 Buffer *b = t->buffer;
1768 /* avoid match at current cursor position */
1769 Row *start_row = b->curs_row;
1770 int start_col = b->curs_col + direction;
1771 if (start_col >= b->cols) {
1772 start_col = 0;
1773 start_row = buffer_next_row(b, start_row, 1);
1774 } else if (start_col < 0) {
1775 start_col = b->cols - 1;
1776 start_row = buffer_next_row(b, start_row, -1);
1779 Row *row = start_row, *matched_row = NULL;
1780 int matched_col = 0;
1781 int len = wcslen(searchbuf) - 1;
1782 int s_start = direction > 0 ? 0 : len;
1783 int s_end = direction > 0 ? len : 0;
1784 int s = s_start;
1786 for (;;) {
1787 int col = direction > 0 ? 0 : b->cols - 1;
1788 if (row == start_row)
1789 col = start_col;
1790 for (; col >= 0 && col < b->cols; col += direction) {
1791 if (searchbuf[s] == row->cells[col].text) {
1792 if (s == s_start) {
1793 matched_row = row;
1794 matched_col = col;
1796 if (s == s_end) {
1797 b->curs_col = matched_col;
1798 if (matched_row)
1799 row_show(t, matched_row);
1800 return;
1802 s += direction;
1803 int width = wcwidth(searchbuf[s]);
1804 if (width < 0)
1805 width = 0;
1806 else if (width >= 1)
1807 width--;
1808 col += direction > 0 ? width : -width;
1809 } else if (searchbuf[s_start] == row->cells[col].text) {
1810 s = s_start + direction;
1811 matched_row = row;
1812 matched_col = col;
1813 } else {
1814 s = s_start;
1818 if ((row = buffer_next_row(b, row, direction)) == start_row)
1819 break;
1823 static void cmdline_hide_callback(void *t)
1825 Buffer *b = ((Vt *)t)->buffer;
1826 b->lines[b->rows - 1].dirty = true;
1829 static void cmdline_show(Cmdline *c, char prefix, int width, void (*callback)(void *t), void *data)
1831 if (!c)
1832 return;
1833 c->callback = callback;
1834 memset(&c->ps, 0, sizeof(mbstate_t));
1835 if (!c->buf) {
1836 c->size = width+1;
1837 c->buf = calloc(c->size, sizeof(wchar_t));
1838 c->cursor = c->end = c->display = c->buf;
1840 if (!c->buf)
1841 return;
1842 c->state = CMDLINE_INIT;
1843 c->data = data;
1844 c->prefix = prefix;
1845 c->width = width - (prefix ? 1 : 0);
1848 static void cmdline_hide(Cmdline *c)
1850 if (!c)
1851 return;
1852 c->state = CMDLINE_INACTIVE;
1853 c->callback(c->data);
1856 static void cmdline_adjust_cursor_pos(Cmdline *c)
1858 int pos = 0, w;
1859 for (wchar_t *cur = c->display; cur < c->cursor; cur++)
1860 pos += xwcwidth(*cur);
1861 int extraspace = pos - c->width + 1;
1862 if (extraspace > 0) {
1863 for (w = 0; w < extraspace; w += xwcwidth(*c->display++));
1864 c->cursor_pos = pos - w;
1865 } else
1866 c->cursor_pos = pos;
1869 static void cmdline_keypress(Cmdline *c, int keycode)
1871 if (keycode != KEY_UP && c->state == CMDLINE_INIT) {
1872 c->buf[0] = L'\0';
1873 c->display = c->cursor = c->end = c->buf;
1874 c->cursor_pos = 0;
1876 char keychar = (char)keycode;
1877 wchar_t wc = L'\0';
1878 int width = -1;
1879 ssize_t len;
1880 size_t n = (c->end - c->cursor) * sizeof(wchar_t);
1881 switch (keycode) {
1882 case KEY_DC:
1883 if (c->cursor == c->end) /* n == 0 */
1884 break;
1885 memmove(c->cursor, c->cursor + 1, n - sizeof(wchar_t));
1886 c->end--;
1887 *c->end = L'\0';
1888 cmdline_adjust_cursor_pos(c);
1889 break;
1890 case KEY_BACKSPACE:
1891 if (c->cursor == c->buf)
1892 break;
1893 memmove(c->cursor - 1, c->cursor, n);
1894 if (c->end > c->buf)
1895 c->end--;
1896 *c->end = L'\0';
1897 case KEY_LEFT:
1898 if (c->cursor > c->buf)
1899 c->cursor--;
1900 if (c->cursor_pos == 0) {
1901 c->display -= c->width / 2;
1902 if (c->display < c->buf)
1903 c->display = c->buf;
1905 cmdline_adjust_cursor_pos(c);
1906 break;
1907 case KEY_UP:
1908 break;
1909 case KEY_DOWN:
1910 c->buf[0] = L'\0';
1911 c->end = c->buf;
1912 case KEY_HOME:
1913 c->display = c->cursor = c->buf;
1914 c->cursor_pos = 0;
1915 break;
1916 case KEY_END:
1917 c->cursor = c->end;
1918 width = 0;
1919 wchar_t *disp = c->end;
1920 while (disp >= c->buf) {
1921 int tmp = xwcwidth(*disp);
1922 if (width + tmp > c->width - 1)
1923 break;
1924 width += tmp;
1925 disp--;
1927 c->display = disp >= c->buf ? disp + 1: c->buf;
1928 c->cursor_pos = (width < c->width - 1) ? width : c->width - 1;
1929 break;
1930 case 1 ... 26: /* CTRL('a') ... CTRL('z') */
1931 if (keycode != '\n')
1932 break;
1933 copymode_search(c->data, c->prefix == '/' ? 1 : -1);
1934 case '\e':
1935 cmdline_hide(c);
1936 return;
1937 default:
1938 len = (ssize_t)mbrtowc(&wc, &keychar, 1, &c->ps);
1939 if (len == -2)
1940 return;
1941 if (len == -1)
1942 wc = keycode;
1944 width = xwcwidth(wc);
1946 if (c->end - c->buf >= c->size - 2) {
1947 c->size *= 2;
1948 wchar_t *buf = realloc(c->buf, c->size * sizeof(wchar_t));
1949 if (!buf)
1950 return;
1951 ptrdiff_t diff = buf - c->buf;
1952 c->cursor += diff;
1953 c->end += diff;
1954 c->display += diff;
1955 c->buf = buf;
1958 if (c->cursor < c->end)
1959 memmove(c->cursor + 1, c->cursor, n);
1960 *c->cursor = wc;
1961 *(++c->end) = L'\0';
1962 case KEY_RIGHT:
1963 if (c->cursor_pos == c->width - 1) {
1964 if (c->cursor < c->end)
1965 c->cursor++;
1966 if (c->cursor != c->end) {
1967 c->display += c->width / 2;
1968 if (c->display > c->end)
1969 c->display = c->buf;
1971 cmdline_adjust_cursor_pos(c);
1972 } else {
1973 if (width == -1)
1974 width = xwcwidth(*c->cursor);
1975 c->cursor_pos += width;
1976 if (c->cursor_pos > c->width - 1)
1977 c->cursor_pos = c->width - 1;
1978 if (c->cursor < c->end)
1979 c->cursor++;
1981 break;
1983 c->state = CMDLINE_ACTIVE;
1986 static void cmdline_free(Cmdline *c)
1988 if (!c)
1989 return;
1990 free(c->buf);
1991 free(c);
1994 void vt_copymode_keypress(Vt *t, int keycode)
1996 Buffer *b = t->buffer;
1997 Row *start_row, *end_row;
1998 int direction, col, start_col, end_col, delta, scroll_page = b->rows / 2;
1999 char *copybuf, keychar = (char)keycode;
2000 bool found;
2002 if (!t->copymode)
2003 return;
2005 if (t->cmdline && t->cmdline->state) {
2006 cmdline_keypress(t->cmdline, keycode);
2007 } else {
2008 switch (keycode) {
2009 case '0' ... '9':
2010 t->copymode_cmd_multiplier = (t->copymode_cmd_multiplier * 10) + (keychar - '0');
2011 return;
2012 case KEY_PPAGE:
2013 delta = b->curs_row - b->lines;
2014 if (delta > scroll_page)
2015 b->curs_row -= scroll_page;
2016 else {
2017 b->curs_row = b->lines;
2018 vt_scroll(t, delta - scroll_page);
2020 break;
2021 case KEY_NPAGE:
2022 delta = b->rows - (b->curs_row - b->lines);
2023 if (delta > scroll_page)
2024 b->curs_row += scroll_page;
2025 else {
2026 b->curs_row = b->lines + b->rows - 1;
2027 vt_scroll(t, scroll_page - delta);
2029 break;
2030 case 'g':
2031 if (b->scroll_amount_above)
2032 vt_scroll(t, -b->scroll_amount_above);
2033 /* fall through */
2034 case 'H':
2035 b->curs_row = b->lines;
2036 break;
2037 case 'M':
2038 b->curs_row = b->lines + (b->rows / 2);
2039 break;
2040 case 'G':
2041 vt_noscroll(t);
2042 /* fall through */
2043 case 'L':
2044 b->curs_row = b->lines + b->rows - 1;
2045 break;
2046 case KEY_HOME:
2047 case '^':
2048 b->curs_col = 0;
2049 break;
2050 case KEY_END:
2051 case '$':
2052 start_col = b->cols - 1;
2053 for (int i = 0; i < b->cols; i++)
2054 if (b->curs_row->cells[i].text)
2055 start_col = i;
2056 b->curs_col = start_col;
2057 break;
2058 case '/':
2059 case '?':
2060 if (!t->cmdline)
2061 t->cmdline = calloc(1, sizeof(Cmdline));
2062 cmdline_show(t->cmdline, keycode, b->cols, cmdline_hide_callback, t);
2063 break;
2064 case 'n':
2065 case 'N':
2066 copymode_search(t, keycode == 'n' ? 1 : -1);
2067 break;
2068 case 'v':
2069 t->copymode_selecting = true;
2070 t->copymode_sel_start_row = b->curs_row;
2071 t->copymode_sel_start_col = b->curs_col;
2072 break;
2073 case 'y':
2074 if (!t->copymode_selecting) {
2075 b->curs_col = 0;
2076 t->copymode_sel_start_row = b->curs_row +
2077 (t->copymode_cmd_multiplier ? t->copymode_cmd_multiplier - 1 : 0);
2078 if (t->copymode_sel_start_row >= b->lines + b->rows)
2079 t->copymode_sel_start_row = b->lines + b->rows - 1;
2080 t->copymode_sel_start_col = b->cols - 1;
2083 copymode_get_selection_boundry(t, &start_row, &start_col, &end_row, &end_col, false);
2084 int line_count = t->copymode_sel_start_row > b->curs_row ?
2085 t->copymode_sel_start_row - b->curs_row :
2086 b->curs_row - t->copymode_sel_start_row;
2087 copybuf = calloc(1, (line_count + 1) * b->cols * MB_CUR_MAX + 1);
2089 if (copybuf) {
2090 char *s = copybuf;
2091 mbstate_t ps;
2092 memset(&ps, 0, sizeof(ps));
2093 Row *row = start_row;
2094 for (;;) {
2095 char *last_non_space = s;
2096 int j = (row == start_row) ? start_col : 0;
2097 int col = (row == end_row) ? end_col : b->cols - 1;
2098 for (size_t len = 0; j <= col; j++) {
2099 if (row->cells[j].text) {
2100 len = wcrtomb(s, row->cells[j].text, &ps);
2101 if (len > 0)
2102 s += len;
2103 last_non_space = s;
2104 } else if (len) {
2105 len = 0;
2106 } else {
2107 *s++ = ' ';
2111 s = last_non_space;
2113 if (row == end_row)
2114 break;
2115 else
2116 *s++ = '\n';
2118 row = buffer_next_row(b, row, 1);
2120 *s = '\0';
2121 if (t->event_handler)
2122 t->event_handler(t, VT_EVENT_COPY_TEXT, copybuf);
2124 /* fall through */
2125 case '\e':
2126 case 'q':
2127 vt_copymode_leave(t);
2128 return;
2129 default:
2130 for (int c = 0; c < (t->copymode_cmd_multiplier ? t->copymode_cmd_multiplier : 1); c++) {
2131 int width;
2132 switch (keycode) {
2133 case 'w':
2134 case 'W':
2135 case 'b':
2136 case 'B':
2137 direction = (keycode == 'w' || keycode == 'W') ? 1 : -1;
2138 start_col = (direction > 0) ? 0 : b->cols - 1;
2139 end_col = (direction > 0) ? b->cols - 1 : 0;
2140 col = b->curs_col;
2141 found = false;
2142 do {
2143 for (;;) {
2144 if (b->curs_row->cells[col].text == ' ') {
2145 found = true;
2146 break;
2149 if (col == end_col)
2150 break;
2151 col += direction;
2154 if (found) {
2155 while (b->curs_row->cells[col].text == ' ') {
2156 if (col == end_col) {
2157 b->curs_row += direction;
2158 break;
2160 col += direction;
2162 } else {
2163 col = start_col;
2164 b->curs_row += direction;
2167 if (b->curs_row < b->lines) {
2168 b->curs_row = b->lines;
2169 if (b->scroll_amount_above)
2170 vt_scroll(t, -1);
2171 else
2172 break;
2175 if (b->curs_row >= b->lines + b->rows) {
2176 b->curs_row = b->lines + b->rows - 1;
2177 if (b->scroll_amount_below)
2178 vt_scroll(t, 1);
2179 else
2180 break;
2182 } while (!found);
2184 if (found)
2185 b->curs_col = col;
2186 break;
2187 case KEY_UP:
2188 case 'k':
2189 if (b->curs_row == b->lines)
2190 vt_scroll(t, -1);
2191 else
2192 b->curs_row--;
2193 break;
2194 case KEY_DOWN:
2195 case 'j':
2196 if (b->curs_row == b->lines + b->rows - 1)
2197 vt_scroll(t, 1);
2198 else
2199 b->curs_row++;
2200 break;
2201 case KEY_RIGHT:
2202 case 'l':
2203 width = wcwidth(b->curs_row->cells[b->curs_col].text);
2204 b->curs_col += width < 1 ? 1 : width;
2205 if (b->curs_col >= b->cols) {
2206 b->curs_col = b->cols - 1;
2207 t->copymode_cmd_multiplier = 0;
2209 break;
2210 case KEY_LEFT:
2211 case 'h':
2212 width = 1;
2213 if (b->curs_col >= 2 && !b->curs_row->cells[b->curs_col-1].text)
2214 width = wcwidth(b->curs_row->cells[b->curs_col-2].text);
2215 b->curs_col -= width < 1 ? 1 : width;
2216 if (b->curs_col < 0) {
2217 b->curs_col = 0;
2218 t->copymode_cmd_multiplier = 0;
2220 break;
2223 break;
2226 if (t->copymode_selecting)
2227 vt_dirty(t);
2228 t->copymode_cmd_multiplier = 0;
2231 void vt_mouse(Vt *t, int x, int y, mmask_t mask)
2233 #ifdef NCURSES_MOUSE_VERSION
2234 char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0;
2236 if (!t->mousetrack)
2237 return;
2239 if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED))
2240 button = 0;
2241 else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED))
2242 button = 1;
2243 else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED))
2244 button = 2;
2245 else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED))
2246 button = 3;
2248 if (mask & BUTTON_SHIFT)
2249 state |= 4;
2250 if (mask & BUTTON_ALT)
2251 state |= 8;
2252 if (mask & BUTTON_CTRL)
2253 state |= 16;
2255 seq[3] = 32 + button + state;
2256 seq[4] = 32 + x;
2257 seq[5] = 32 + y;
2259 vt_write(t, seq, sizeof seq);
2261 if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) {
2262 /* send a button release event */
2263 button = 3;
2264 seq[3] = 32 + button + state;
2265 vt_write(t, seq, sizeof seq);
2267 #endif /* NCURSES_MOUSE_VERSION */
2270 static unsigned int color_hash(short fg, short bg)
2272 if (fg == -1)
2273 fg = COLORS;
2274 if (bg == -1)
2275 bg = COLORS + 1;
2276 return fg * (COLORS + 2) + bg;
2279 short vt_color_get(Vt *t, short fg, short bg)
2281 if (fg >= COLORS)
2282 fg = (t ? t->deffg : default_fg);
2283 if (bg >= COLORS)
2284 bg = (t ? t->defbg : default_bg);
2286 if (!has_default_colors) {
2287 if (fg == -1)
2288 fg = (t && t->deffg != -1 ? t->deffg : default_fg);
2289 if (bg == -1)
2290 bg = (t && t->defbg != -1 ? t->defbg : default_bg);
2293 if (!color2palette || (fg == -1 && bg == -1))
2294 return 0;
2295 unsigned int index = color_hash(fg, bg);
2296 if (color2palette[index] == 0) {
2297 short oldfg, oldbg;
2298 for (;;) {
2299 if (++color_pair_current >= color_pairs_max)
2300 color_pair_current = color_pairs_reserved + 1;
2301 pair_content(color_pair_current, &oldfg, &oldbg);
2302 unsigned int old_index = color_hash(oldfg, oldbg);
2303 if (color2palette[old_index] >= 0) {
2304 if (init_pair(color_pair_current, fg, bg) == OK) {
2305 color2palette[old_index] = 0;
2306 color2palette[index] = color_pair_current;
2308 break;
2313 short color_pair = color2palette[index];
2314 return color_pair >= 0 ? color_pair : -color_pair;
2317 short vt_color_reserve(short fg, short bg)
2319 if (!color2palette || fg >= COLORS || bg >= COLORS)
2320 return 0;
2321 if (!has_default_colors && fg == -1)
2322 fg = default_fg;
2323 if (!has_default_colors && bg == -1)
2324 bg = default_bg;
2325 if (fg == -1 && bg == -1)
2326 return 0;
2327 unsigned int index = color_hash(fg, bg);
2328 if (color2palette[index] >= 0) {
2329 if (init_pair(++color_pairs_reserved, fg, bg) == OK)
2330 color2palette[index] = -color_pairs_reserved;
2332 short color_pair = color2palette[index];
2333 return color_pair >= 0 ? color_pair : -color_pair;
2336 static void init_colors(void)
2338 pair_content(0, &default_fg, &default_bg);
2339 if (default_fg == -1)
2340 default_fg = COLOR_WHITE;
2341 if (default_bg == -1)
2342 default_bg = COLOR_BLACK;
2343 has_default_colors = (use_default_colors() == OK);
2344 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
2345 if (COLORS)
2346 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
2347 vt_color_reserve(COLOR_WHITE, COLOR_BLACK);
2350 void vt_init(void)
2352 init_colors();
2353 is_utf8_locale();
2354 char color_suffix[] = "-256color";
2355 char *term = getenv("DVTM_TERM");
2356 if (term)
2357 strncpy(vt_term, term, sizeof(vt_term) - sizeof(color_suffix));
2358 if (COLORS >= 256)
2359 strncat(vt_term, color_suffix, sizeof(color_suffix) - 1);
2362 void vt_set_keytable(const char * const keytable_overlay[], int count)
2364 for (int k = 0; k < count && k < KEY_MAX; k++) {
2365 const char *keyseq = keytable_overlay[k];
2366 if (keyseq)
2367 keytable[k] = keyseq;
2371 void vt_shutdown(void)
2373 free(color2palette);
2376 void vt_set_escseq_handler(Vt *t, vt_escseq_handler_t handler)
2378 t->escseq_handler = handler;
2381 void vt_set_event_handler(Vt *t, vt_event_handler_t handler)
2383 t->event_handler = handler;
2386 void vt_set_data(Vt *t, void *data)
2388 t->data = data;
2391 void *vt_get_data(Vt *t)
2393 return t->data;
2396 unsigned vt_cursor(Vt *t)
2398 if (t->copymode)
2399 return 1;
2400 return t->buffer->scroll_amount_below ? 0 : !t->curshid;
2403 unsigned vt_copymode(Vt *t)
2405 return t->copymode;
2408 void vt_copymode_enter(Vt *t)
2410 if (t->copymode)
2411 return;
2412 Buffer *b = t->buffer;
2413 t->copymode_curs_srow = b->curs_row - b->lines;
2414 t->copymode_curs_scol = b->curs_col;
2415 t->copymode = true;
2418 void vt_copymode_leave(Vt *t)
2420 if (!t->copymode)
2421 return;
2422 Buffer *b = t->buffer;
2423 t->copymode = false;
2424 t->copymode_selecting = false;
2425 t->copymode_sel_start_row = b->lines;
2426 t->copymode_sel_start_col = 0;
2427 t->copymode_cmd_multiplier = 0;
2428 b->curs_row = b->lines + t->copymode_curs_srow;
2429 b->curs_col = t->copymode_curs_scol;
2430 cmdline_hide(t->cmdline);
2431 vt_noscroll(t);
2432 vt_dirty(t);