2 * Copyright © 2004 Bruno T. C. de Oliveira
3 * Copyright © 2006 Pierre Habouzit
4 * Copyright © 2008-2012 Marc Andre 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.
28 #include <sys/ioctl.h>
29 #include <sys/types.h>
32 #if defined(__linux__) || defined(__CYGWIN__)
34 #elif defined(__FreeBSD__)
36 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
39 #if defined(__CYGWIN__) || defined(_AIX)
46 # include "forkpty-aix.c"
49 #ifndef NCURSES_ATTR_SHIFT
50 # define NCURSES_ATTR_SHIFT 8
55 # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)])
56 # else /* BSD curses */
57 # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)])
61 #ifdef NCURSES_VERSION
62 # ifndef NCURSES_EXT_COLORS
63 # define NCURSES_EXT_COLORS 0
65 # if !NCURSES_EXT_COLORS
66 # define MAX_COLOR_PAIRS 256
69 #ifndef MAX_COLOR_PAIRS
70 # define MAX_COLOR_PAIRS COLOR_PAIRS
73 #define IS_CONTROL(ch) !((ch) & 0xffffff60UL)
74 #define MIN(x, y) ((x) < (y) ? (x) : (y))
75 #define sstrlen(str) (sizeof(str) - 1)
77 static bool is_utf8
, has_default_colors
;
78 static short color_pairs_reserved
, color_pairs_max
, color_pair_current
;
79 static short *color2palette
, default_fg
, default_bg
;
83 C0_SOH
, C0_STX
, C0_ETX
, C0_EOT
, C0_ENQ
, C0_ACK
, C0_BEL
,
84 C0_BS
, C0_HT
, C0_LF
, C0_VT
, C0_FF
, C0_CR
, C0_SO
, C0_SI
,
85 C0_DLE
, C0_DC1
, C0_DC2
, D0_DC3
, C0_DC4
, C0_NAK
, C0_SYN
, C0_ETB
,
86 C0_CAN
, C0_EM
, C0_SUB
, C0_ESC
, C0_IS4
, C0_IS3
, C0_IS2
, C0_IS1
,
91 C1_41
, C1_BPH
, C1_NBH
, C1_44
, C1_NEL
, C1_SSA
, C1_ESA
,
92 C1_HTS
, C1_HTJ
, C1_VTS
, C1_PLD
, C1_PLU
, C1_RI
, C1_SS2
, C1_SS3
,
93 C1_DCS
, C1_PU1
, C1_PU2
, C1_STS
, C1_CCH
, C1_MW
, C1_SPA
, C1_EPA
,
94 C1_SOS
, C1_59
, C1_SCI
, C1_CSI
, CS_ST
, C1_OSC
, C1_PM
, C1_APC
,
99 CSI_CUU
, CSI_CUD
, CSI_CUF
, CSI_CUB
, CSI_CNL
, CSI_CPL
, CSI_CHA
,
100 CSI_CUP
, CSI_CHT
, CSI_ED
, CSI_EL
, CSI_IL
, CSI_DL
, CSI_EF
, CSI_EA
,
101 CSI_DCH
, CSI_SEE
, CSI_CPR
, CSI_SU
, CSI_SD
, CSI_NP
, CSI_PP
, CSI_CTC
,
102 CSI_ECH
, CSI_CVT
, CSI_CBT
, CSI_SRS
, CSI_PTX
, CSI_SDS
, CSI_SIMD
, CSI_5F
,
103 CSI_HPA
, CSI_HPR
, CSI_REP
, CSI_DA
, CSI_VPA
, CSI_VPR
, CSI_HVP
, CSI_TBC
,
104 CSI_SM
, CSI_MC
, CSI_HPB
, CSI_VPB
, CSI_RM
, CSI_SGR
, CSI_DSR
, CSI_DAQ
,
105 CSI_70
, CSI_71
, CSI_72
, CSI_73
, CSI_74
, CSI_75
, CSI_76
, CSI_77
,
106 CSI_78
, CSI_79
, CSI_7A
, CSI_7B
, CSI_7C
, CSI_7D
, CSI_7E
, CSI_7F
131 int rows
, cols
, maxcols
;
132 unsigned curattrs
, savattrs
;
133 int curs_col
, curs_srow
, curs_scol
;
134 short curfg
, curbg
, savfg
, savbg
;
138 Buffer buffer_normal
;
146 unsigned seen_input
:1;
150 unsigned curskeymode
:1;
152 unsigned relposmode
:1;
153 unsigned mousetrack
:1;
154 unsigned graphmode
:1;
157 /* buffers and parsing state */
161 unsigned int rlen
, elen
;
163 /* xterm style window title */
166 vt_event_handler_t event_handler
;
168 /* custom escape sequence handler */
169 vt_escseq_handler_t escseq_handler
;
173 static char const * const keytable
[KEY_MAX
+1] = {
175 /* for the arrow keys the CSI / SS3 sequences are not stored here
176 * because they depend on the current cursor terminal mode
183 [KEY_SUP
] = "\e[1;2A",
186 [KEY_SDOWN
] = "\e[1;2B",
188 [KEY_SRIGHT
] = "\e[1;2C",
189 [KEY_SLEFT
] = "\e[1;2D",
190 [KEY_BACKSPACE
] = "\177",
193 [KEY_PPAGE
] = "\e[5~",
194 [KEY_NPAGE
] = "\e[6~",
195 [KEY_HOME
] = "\e[7~",
197 [KEY_SUSPEND
] = "\x1A", /* Ctrl+Z gets mapped to this */
198 [KEY_F(1)] = "\e[11~",
199 [KEY_F(2)] = "\e[12~",
200 [KEY_F(3)] = "\e[13~",
201 [KEY_F(4)] = "\e[14~",
202 [KEY_F(5)] = "\e[15~",
203 [KEY_F(6)] = "\e[17~",
204 [KEY_F(7)] = "\e[18~",
205 [KEY_F(8)] = "\e[19~",
206 [KEY_F(9)] = "\e[20~",
207 [KEY_F(10)] = "\e[21~",
208 [KEY_F(11)] = "\e[23~",
209 [KEY_F(12)] = "\e[24~",
210 [KEY_F(13)] = "\e[25~",
211 [KEY_F(14)] = "\e[26~",
212 [KEY_F(15)] = "\e[28~",
213 [KEY_F(16)] = "\e[29~",
214 [KEY_F(17)] = "\e[31~",
215 [KEY_F(18)] = "\e[32~",
216 [KEY_F(19)] = "\e[33~",
217 [KEY_F(20)] = "\e[34~",
221 static void process_nonprinting(Vt
*t
, wchar_t wc
);
222 static void send_curs(Vt
*t
);
224 __attribute__ ((const))
225 static uint16_t build_attrs(unsigned curattrs
)
227 return ((curattrs
& ~A_COLOR
) | COLOR_PAIR(curattrs
& 0xff))
228 >> NCURSES_ATTR_SHIFT
;
231 static void row_set(Row
*row
, int start
, int len
, Buffer
*t
)
235 .attr
= t
? build_attrs(t
->curattrs
) : 0,
236 .fg
= t
? t
->curfg
: -1,
237 .bg
= t
? t
->curbg
: -1,
240 for (int i
= start
; i
< len
+ start
; i
++)
241 row
->cells
[i
] = cell
;
245 static void row_roll(Row
*start
, Row
*end
, int count
)
254 Row
*buf
= alloca(count
* sizeof(Row
));
256 memcpy(buf
, start
, count
* sizeof(Row
));
257 memmove(start
, start
+ count
, (n
- count
) * sizeof(Row
));
258 memcpy(end
- count
, buf
, count
* sizeof(Row
));
259 for (Row
*row
= start
; row
< end
; row
++)
264 static void clamp_cursor_to_bounds(Vt
*vt
)
266 Buffer
*t
= vt
->buffer
;
267 Row
*lines
= vt
->relposmode
? t
->scroll_top
: t
->lines
;
268 int rows
= vt
->relposmode
? t
->scroll_bot
- t
->scroll_top
: t
->rows
;
270 if (t
->curs_row
< lines
)
272 if (t
->curs_row
>= lines
+ rows
)
273 t
->curs_row
= lines
+ rows
- 1;
276 if (t
->curs_col
>= t
->cols
)
277 t
->curs_col
= t
->cols
- 1;
280 static void save_curs(Vt
*vt
)
282 Buffer
*t
= vt
->buffer
;
283 t
->curs_srow
= t
->curs_row
- t
->lines
;
284 t
->curs_scol
= t
->curs_col
;
287 static void restore_curs(Vt
*vt
)
289 Buffer
*t
= vt
->buffer
;
290 t
->curs_row
= t
->lines
+ t
->curs_srow
;
291 t
->curs_col
= t
->curs_scol
;
292 clamp_cursor_to_bounds(vt
);
295 static void save_attrs(Vt
*vt
)
297 Buffer
*t
= vt
->buffer
;
298 t
->savattrs
= t
->curattrs
;
303 static void restore_attrs(Vt
*vt
)
305 Buffer
*t
= vt
->buffer
;
306 t
->curattrs
= t
->savattrs
;
311 static void fill_scroll_buf(Buffer
*t
, int s
)
313 /* work in screenfuls */
314 int ssz
= t
->scroll_bot
- t
->scroll_top
;
316 fill_scroll_buf(t
, ssz
);
317 fill_scroll_buf(t
, s
- ssz
);
321 fill_scroll_buf(t
, -ssz
);
322 fill_scroll_buf(t
, s
+ ssz
);
326 t
->scroll_buf_len
+= s
;
327 if (t
->scroll_buf_len
>= t
->scroll_buf_sz
)
328 t
->scroll_buf_len
= t
->scroll_buf_sz
;
330 if (s
> 0 && t
->scroll_buf_sz
) {
331 for (int i
= 0; i
< s
; i
++) {
332 Row tmp
= t
->scroll_top
[i
];
333 t
->scroll_top
[i
] = t
->scroll_buf
[t
->scroll_buf_ptr
];
334 t
->scroll_buf
[t
->scroll_buf_ptr
] = tmp
;
337 if (t
->scroll_buf_ptr
== t
->scroll_buf_sz
)
338 t
->scroll_buf_ptr
= 0;
341 row_roll(t
->scroll_top
, t
->scroll_bot
, s
);
342 if (s
< 0 && t
->scroll_buf_sz
) {
343 for (int i
= (-s
) - 1; i
>= 0; i
--) {
345 if (t
->scroll_buf_ptr
== -1)
346 t
->scroll_buf_ptr
= t
->scroll_buf_sz
- 1;
348 Row tmp
= t
->scroll_top
[i
];
349 t
->scroll_top
[i
] = t
->scroll_buf
[t
->scroll_buf_ptr
];
350 t
->scroll_buf
[t
->scroll_buf_ptr
] = tmp
;
351 t
->scroll_top
[i
].dirty
= true;
356 static void cursor_line_down(Vt
*vt
)
358 Buffer
*t
= vt
->buffer
;
359 row_set(t
->curs_row
, t
->cols
, t
->maxcols
- t
->cols
, 0);
361 if (t
->curs_row
< t
->scroll_bot
)
366 t
->curs_row
= t
->scroll_bot
- 1;
367 fill_scroll_buf(t
, 1);
368 row_set(t
->curs_row
, 0, t
->cols
, t
);
371 static void new_escape_sequence(Vt
*t
)
378 static void cancel_escape_sequence(Vt
*t
)
385 static bool is_valid_csi_ender(int c
)
387 return (c
>= 'a' && c
<= 'z')
388 || (c
>= 'A' && c
<= 'Z')
389 || (c
== '@' || c
== '`');
392 /* interprets a 'set attribute' (SGR) CSI escape sequence */
393 static void interpret_csi_SGR(Vt
*vt
, int param
[], int pcount
)
395 Buffer
*t
= vt
->buffer
;
397 /* special case: reset attributes */
398 t
->curattrs
= A_NORMAL
;
399 t
->curfg
= t
->curbg
= -1;
403 for (int i
= 0; i
< pcount
; i
++) {
406 t
->curattrs
= A_NORMAL
;
407 t
->curfg
= t
->curbg
= -1;
410 t
->curattrs
|= A_BOLD
;
413 t
->curattrs
|= A_UNDERLINE
;
416 t
->curattrs
|= A_BLINK
;
419 t
->curattrs
|= A_REVERSE
;
422 t
->curattrs
|= A_INVIS
;
425 t
->curattrs
&= ~A_BOLD
;
428 t
->curattrs
&= ~A_UNDERLINE
;
431 t
->curattrs
&= ~A_BLINK
;
434 t
->curattrs
&= ~A_REVERSE
;
437 t
->curattrs
&= ~A_INVIS
;
439 case 30 ... 37: /* fg */
440 t
->curfg
= param
[i
] - 30;
443 if ((i
+ 2) < pcount
&& param
[i
+ 1] == 5) {
444 t
->curfg
= param
[i
+ 2];
451 case 40 ... 47: /* bg */
452 t
->curbg
= param
[i
] - 40;
455 if ((i
+ 2) < pcount
&& param
[i
+ 1] == 5) {
456 t
->curbg
= param
[i
+ 2];
463 case 90 ... 97: /* hi fg */
464 t
->curfg
= param
[i
] - 82;
466 case 100 ... 107: /* hi bg */
467 t
->curbg
= param
[i
] - 92;
475 /* interprets an 'erase display' (ED) escape sequence */
476 static void interpret_csi_ED(Vt
*vt
, int param
[], int pcount
)
478 Row
*row
, *start
, *end
;
479 Buffer
*t
= vt
->buffer
;
482 t
->curattrs
= A_NORMAL
;
483 t
->curfg
= t
->curbg
= -1;
485 if (pcount
&& param
[0] == 2) {
487 end
= t
->lines
+ t
->rows
;
488 } else if (pcount
&& param
[0] == 1) {
491 row_set(t
->curs_row
, 0, t
->curs_col
+ 1, t
);
493 row_set(t
->curs_row
, t
->curs_col
, t
->cols
- t
->curs_col
, t
);
494 start
= t
->curs_row
+ 1;
495 end
= t
->lines
+ t
->rows
;
498 for (row
= start
; row
< end
; row
++)
499 row_set(row
, 0, t
->cols
, t
);
504 /* interprets a 'move cursor' (CUP) escape sequence */
505 static void interpret_csi_CUP(Vt
*vt
, int param
[], int pcount
)
507 Buffer
*t
= vt
->buffer
;
508 Row
*lines
= vt
->relposmode
? t
->scroll_top
: t
->lines
;
513 } else if (pcount
== 1) {
514 t
->curs_row
= lines
+ param
[0] - 1;
517 t
->curs_row
= lines
+ param
[0] - 1;
518 t
->curs_col
= param
[1] - 1;
521 clamp_cursor_to_bounds(vt
);
524 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
525 * CPL, CHA, HPR, VPA, VPR, HPA */
526 static void interpret_csi_C(Vt
*vt
, char verb
, int param
[], int pcount
)
528 Buffer
*t
= vt
->buffer
;
529 int n
= (pcount
&& param
[0] > 0) ? param
[0] : 1;
556 t
->curs_col
= param
[0] - 1;
559 t
->curs_row
= t
->lines
+ param
[0] - 1;
563 clamp_cursor_to_bounds(vt
);
566 /* Interpret the 'erase line' escape sequence */
567 static void interpret_csi_EL(Vt
*vt
, int param
[], int pcount
)
569 Buffer
*t
= vt
->buffer
;
570 switch (pcount
? param
[0] : 0) {
572 row_set(t
->curs_row
, 0, t
->curs_col
+ 1, t
);
575 row_set(t
->curs_row
, 0, t
->cols
, t
);
578 row_set(t
->curs_row
, t
->curs_col
, t
->cols
- t
->curs_col
, t
);
583 /* Interpret the 'insert blanks' sequence (ICH) */
584 static void interpret_csi_ICH(Vt
*vt
, int param
[], int pcount
)
586 Buffer
*t
= vt
->buffer
;
587 Row
*row
= t
->curs_row
;
588 int n
= (pcount
&& param
[0] > 0) ? param
[0] : 1;
590 if (t
->curs_col
+ n
> t
->cols
)
591 n
= t
->cols
- t
->curs_col
;
593 for (int i
= t
->cols
- 1; i
>= t
->curs_col
+ n
; i
--)
594 row
->cells
[i
] = row
->cells
[i
- n
];
596 row_set(row
, t
->curs_col
, n
, t
);
599 /* Interpret the 'delete chars' sequence (DCH) */
600 static void interpret_csi_DCH(Vt
*vt
, int param
[], int pcount
)
602 Buffer
*t
= vt
->buffer
;
603 Row
*row
= t
->curs_row
;
604 int n
= (pcount
&& param
[0] > 0) ? param
[0] : 1;
606 if (t
->curs_col
+ n
> t
->cols
)
607 n
= t
->cols
- t
->curs_col
;
609 for (int i
= t
->curs_col
; i
< t
->cols
- n
; i
++)
610 row
->cells
[i
] = row
->cells
[i
+ n
];
612 row_set(row
, t
->cols
- n
, n
, t
);
615 /* Interpret an 'insert line' sequence (IL) */
616 static void interpret_csi_IL(Vt
*vt
, int param
[], int pcount
)
618 Buffer
*t
= vt
->buffer
;
619 int n
= (pcount
&& param
[0] > 0) ? param
[0] : 1;
621 if (t
->curs_row
+ n
>= t
->scroll_bot
) {
622 for (Row
*row
= t
->curs_row
; row
< t
->scroll_bot
; row
++)
623 row_set(row
, 0, t
->cols
, t
);
625 row_roll(t
->curs_row
, t
->scroll_bot
, -n
);
626 for (Row
*row
= t
->curs_row
; row
< t
->curs_row
+ n
; row
++)
627 row_set(row
, 0, t
->cols
, t
);
631 /* Interpret a 'delete line' sequence (DL) */
632 static void interpret_csi_DL(Vt
*vt
, int param
[], int pcount
)
634 Buffer
*t
= vt
->buffer
;
635 int n
= (pcount
&& param
[0] > 0) ? param
[0] : 1;
637 if (t
->curs_row
+ n
>= t
->scroll_bot
) {
638 for (Row
*row
= t
->curs_row
; row
< t
->scroll_bot
; row
++)
639 row_set(row
, 0, t
->cols
, t
);
641 row_roll(t
->curs_row
, t
->scroll_bot
, n
);
642 for (Row
*row
= t
->scroll_bot
- n
; row
< t
->scroll_bot
; row
++)
643 row_set(row
, 0, t
->cols
, t
);
647 /* Interpret an 'erase characters' (ECH) sequence */
648 static void interpret_csi_ECH(Vt
*vt
, int param
[], int pcount
)
650 Buffer
*t
= vt
->buffer
;
651 int n
= (pcount
&& param
[0] > 0) ? param
[0] : 1;
653 if (t
->curs_col
+ n
> t
->cols
)
654 n
= t
->cols
- t
->curs_col
;
656 row_set(t
->curs_row
, t
->curs_col
, n
, t
);
659 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
660 static void interpret_csi_DECSTBM(Vt
*vt
, int param
[], int pcount
)
662 Buffer
*t
= vt
->buffer
;
663 int new_top
, new_bot
;
667 t
->scroll_top
= t
->lines
;
668 t
->scroll_bot
= t
->lines
+ t
->rows
;
671 new_top
= param
[0] - 1;
674 /* clamp to bounds */
677 if (new_top
>= t
->rows
)
678 new_top
= t
->rows
- 1;
681 if (new_bot
>= t
->rows
)
684 /* check for range validity */
685 if (new_top
< new_bot
) {
686 t
->scroll_top
= t
->lines
+ new_top
;
687 t
->scroll_bot
= t
->lines
+ new_bot
;
691 return; /* malformed */
695 static void es_interpret_csi(Vt
*t
)
697 static int csiparam
[BUFSIZ
];
699 const char *p
= t
->ebuf
+ 1;
700 char verb
= t
->ebuf
[t
->elen
- 1];
702 /* parse numeric parameters */
703 for (p
+= (t
->ebuf
[1] == '?'); *p
; p
++) {
704 if (IS_CONTROL(*p
)) {
705 process_nonprinting(t
, *p
);
706 } else if (*p
== ';') {
707 if (param_count
>= (int)sizeof(csiparam
))
708 return; /* too long! */
709 csiparam
[param_count
++] = 0;
710 } else if (isdigit((unsigned char)*p
)) {
711 if (param_count
== 0)
712 csiparam
[param_count
++] = 0;
713 csiparam
[param_count
- 1] *= 10;
714 csiparam
[param_count
- 1] += *p
- '0';
718 if (t
->ebuf
[1] == '?') {
719 if (verb
== 'h') { /* DEC Private Mode Set (DECSET) */
720 switch (csiparam
[0]) {
721 case 1: /* set ANSI cursor (application) key mode (DECCKM) */
722 t
->curskeymode
= true;
724 case 6: /* set origin to relative (DECOM) */
725 t
->relposmode
= true;
727 case 25: /* make cursor visible (DECCM) */
730 case 47: /* use alternate screen buffer */
731 t
->buffer
->curattrs
= A_NORMAL
;
732 t
->buffer
->curfg
= t
->buffer
->curbg
= -1;
734 case 1000: /* enable normal mouse tracking */
735 t
->mousetrack
= true;
738 } else if (verb
== 'l') { /* DEC Private Mode Reset (DECRST) */
739 switch (csiparam
[0]) {
740 case 1: /* reset ANSI cursor (normal) key mode (DECCKM) */
741 t
->curskeymode
= false;
743 case 6: /* set origin to absolute (DECOM) */
744 t
->relposmode
= false;
746 case 25: /* make cursor visible (DECCM) */
749 case 47: /* use normal screen buffer */
750 t
->buffer
->curattrs
= A_NORMAL
;
751 t
->buffer
->curfg
= t
->buffer
->curbg
= -1;
753 case 1000: /* disable normal mouse tracking */
754 t
->mousetrack
= false;
760 /* delegate handling depending on command character (verb) */
763 if (param_count
== 1 && csiparam
[0] == 4) /* insert mode */
767 if (param_count
== 1 && csiparam
[0] == 4) /* replace mode */
770 case 'm': /* it's a 'set attribute' sequence */
771 interpret_csi_SGR(t
, csiparam
, param_count
);
773 case 'J': /* it's an 'erase display' sequence */
774 interpret_csi_ED(t
, csiparam
, param_count
);
777 case 'f': /* it's a 'move cursor' sequence */
778 interpret_csi_CUP(t
, csiparam
, param_count
);
791 /* it is a 'relative move' */
792 interpret_csi_C(t
, verb
, csiparam
, param_count
);
794 case 'K': /* erase line */
795 interpret_csi_EL(t
, csiparam
, param_count
);
797 case '@': /* insert characters */
798 interpret_csi_ICH(t
, csiparam
, param_count
);
800 case 'P': /* delete characters */
801 interpret_csi_DCH(t
, csiparam
, param_count
);
803 case 'L': /* insert lines */
804 interpret_csi_IL(t
, csiparam
, param_count
);
806 case 'M': /* delete lines */
807 interpret_csi_DL(t
, csiparam
, param_count
);
809 case 'X': /* erase chars */
810 interpret_csi_ECH(t
, csiparam
, param_count
);
812 case 'r': /* set scrolling region */
813 interpret_csi_DECSTBM(t
, csiparam
, param_count
);
815 case 's': /* save cursor location */
818 case 'u': /* restore cursor location */
821 case 'n': /* query cursor location */
822 if (param_count
== 1 && csiparam
[0] == 6)
830 /* Interpret an 'index' (IND) sequence */
831 static void interpret_esc_IND(Vt
*vt
)
833 Buffer
*t
= vt
->buffer
;
834 if (t
->curs_row
< t
->lines
+ t
->rows
- 1)
838 /* Interpret a 'reverse index' (RI) sequence */
839 static void interpret_esc_RI(Vt
*vt
)
841 Buffer
*t
= vt
->buffer
;
842 if (t
->curs_row
> t
->lines
)
845 row_roll(t
->scroll_top
, t
->scroll_bot
, -1);
846 row_set(t
->scroll_top
, 0, t
->cols
, t
);
850 /* Interpret a 'next line' (NEL) sequence */
851 static void interpret_esc_NEL(Vt
*vt
)
853 Buffer
*t
= vt
->buffer
;
854 if (t
->curs_row
< t
->lines
+ t
->rows
- 1) {
860 /* Interpret a 'select character set' (SCS) sequence */
861 static void interpret_esc_SCS(Vt
*t
)
863 /* ESC ( sets G0, ESC ) sets G1 */
864 t
->charsets
[! !(t
->ebuf
[0] == ')')] = (t
->ebuf
[1] == '0');
865 t
->graphmode
= t
->charsets
[0];
868 /* Interpret xterm specific escape sequences */
869 static void interpret_esc_xterm(Vt
*t
)
871 /* ESC]n;dataBEL -- the ESC is not part of t->ebuf */
874 switch (t
->ebuf
[1]) {
877 t
->ebuf
[t
->elen
- 1] = '\0';
878 if (t
->elen
> sstrlen("]n;\a"))
879 title
= t
->ebuf
+ sstrlen("]n;");
881 if (t
->event_handler
)
882 t
->event_handler(t
, VT_EVENT_TITLE
, title
);
886 static void try_interpret_escape_seq(Vt
*t
)
888 char lastchar
= t
->ebuf
[t
->elen
- 1];
893 if (t
->escseq_handler
) {
894 switch ((*(t
->escseq_handler
)) (t
, t
->ebuf
)) {
895 case VT_ESCSEQ_HANDLER_OK
:
896 cancel_escape_sequence(t
);
898 case VT_ESCSEQ_HANDLER_NOTYET
:
899 if (t
->elen
+ 1 >= sizeof(t
->ebuf
))
906 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECALN, DECFPP */
908 cancel_escape_sequence(t
);
915 interpret_esc_SCS(t
);
916 cancel_escape_sequence(t
);
920 case ']': /* xterm thing */
921 if (lastchar
== '\a' ||
922 (lastchar
== '\\' && t
->elen
>= 2 && t
->ebuf
[t
->elen
- 2] == '\e')) {
923 interpret_esc_xterm(t
);
924 cancel_escape_sequence(t
);
929 if (is_valid_csi_ender(lastchar
)) {
931 cancel_escape_sequence(t
);
935 case '7': /* DECSC: save cursor and attributes */
938 cancel_escape_sequence(t
);
940 case '8': /* DECRC: restore cursor and attributes */
943 cancel_escape_sequence(t
);
945 case 'D': /* IND: index */
946 interpret_esc_IND(t
);
947 cancel_escape_sequence(t
);
949 case 'M': /* RI: reverse index */
951 cancel_escape_sequence(t
);
953 case 'E': /* NEL: next line */
954 interpret_esc_NEL(t
);
955 cancel_escape_sequence(t
);
961 if (t
->elen
+ 1 >= sizeof(t
->ebuf
)) {
964 fprintf(stderr
, "cancelled: \\033");
965 for (unsigned int i
= 0; i
< t
->elen
; i
++) {
966 if (isprint(t
->ebuf
[i
])) {
967 fputc(t
->ebuf
[i
], stderr
);
969 fprintf(stderr
, "\\%03o", t
->ebuf
[i
]);
974 cancel_escape_sequence(t
);
978 static void process_nonprinting(Vt
*vt
, wchar_t wc
)
980 Buffer
*t
= vt
->buffer
;
983 new_escape_sequence(vt
);
993 case C0_HT
: /* tab */
994 t
->curs_col
= (t
->curs_col
+ 8) & ~7;
995 if (t
->curs_col
>= t
->cols
)
996 t
->curs_col
= t
->cols
- 1;
1004 cursor_line_down(vt
);
1006 case C0_SO
: /* shift out, invoke the G1 character set */
1007 vt
->graphmode
= vt
->charsets
[1];
1009 case C0_SI
: /* shift in, invoke the G0 character set */
1010 vt
->graphmode
= vt
->charsets
[0];
1015 static void is_utf8_locale(void)
1017 const char *cset
= nl_langinfo(CODESET
) ? : "ANSI_X3.4-1968";
1018 is_utf8
= !strcmp(cset
, "UTF-8");
1021 static wchar_t get_vt100_graphic(char c
)
1023 static char vt100_acs
[] = "`afgjklmnopqrstuvwxyz{|}~";
1026 * 5f-7e standard vt100
1027 * 40-5e rxvt extension for extra curses acs chars
1029 static uint16_t const vt100_utf8
[62] = {
1030 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
1031 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f
1032 0, 0, 0, 0, 0, 0, 0, 0, // 50-57
1033 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f
1034 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
1035 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
1036 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
1037 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e
1041 return vt100_utf8
[c
- 0x41];
1042 else if (strchr(vt100_acs
, c
))
1043 return NCURSES_ACS(c
);
1047 static void put_wc(Vt
*t
, wchar_t wc
)
1051 if (!t
->seen_input
) {
1053 kill(-t
->childpid
, SIGWINCH
);
1057 if (t
->elen
+ 1 < sizeof(t
->ebuf
)) {
1058 t
->ebuf
[t
->elen
] = wc
;
1059 t
->ebuf
[++t
->elen
] = '\0';
1060 try_interpret_escape_seq(t
);
1062 cancel_escape_sequence(t
);
1064 } else if (IS_CONTROL(wc
)) {
1065 process_nonprinting(t
, wc
);
1068 if (wc
>= 0x41 && wc
<= 0x7e) {
1069 wchar_t gc
= get_vt100_graphic(wc
);
1074 } else if ((width
= wcwidth(wc
)) < 1) {
1077 Buffer
*b
= t
->buffer
;
1078 Cell blank_cell
= { L
'\0', build_attrs(b
->curattrs
), b
->curfg
, b
->curbg
};
1079 if (width
== 2 && b
->curs_col
== b
->cols
- 1) {
1080 b
->curs_row
->cells
[b
->curs_col
++] = blank_cell
;
1081 b
->curs_row
->dirty
= true;
1084 if (b
->curs_col
>= b
->cols
) {
1086 cursor_line_down(t
);
1090 Cell
*src
= b
->curs_row
->cells
+ b
->curs_col
;
1091 Cell
*dest
= src
+ width
;
1092 size_t len
= b
->cols
- b
->curs_col
- width
;
1093 memmove(dest
, src
, len
);
1096 b
->curs_row
->cells
[b
->curs_col
] = blank_cell
;
1097 b
->curs_row
->cells
[b
->curs_col
++].text
= wc
;
1098 b
->curs_row
->dirty
= true;
1100 b
->curs_row
->cells
[b
->curs_col
++] = blank_cell
;
1104 int vt_process(Vt
*t
)
1107 unsigned int pos
= 0;
1114 res
= read(t
->pty
, t
->rbuf
+ t
->rlen
, sizeof(t
->rbuf
) - t
->rlen
);
1119 while (pos
< t
->rlen
) {
1123 len
= (ssize_t
)mbrtowc(&wc
, t
->rbuf
+ pos
, t
->rlen
- pos
, &t
->ps
);
1126 memmove(t
->rbuf
, t
->rbuf
+ pos
, t
->rlen
);
1135 pos
+= len
? len
: 1;
1140 memmove(t
->rbuf
, t
->rbuf
+ pos
, t
->rlen
);
1144 void vt_set_default_colors(Vt
*t
, unsigned attrs
, short fg
, short bg
)
1146 t
->defattrs
= attrs
;
1151 void buffer_init(Buffer
*t
, int rows
, int cols
, int scroll_buf_sz
)
1153 Row
*lines
, *scroll_buf
;
1155 t
->maxcols
= t
->cols
= cols
;
1156 t
->curattrs
= A_NORMAL
; /* white text over black background */
1157 t
->curfg
= t
->curbg
= -1;
1158 t
->lines
= lines
= calloc(t
->rows
, sizeof(Row
));
1159 for (Row
*row
= lines
, *end
= lines
+ rows
; row
< end
; row
++)
1160 row
->cells
= calloc(cols
, sizeof(Cell
));
1161 t
->curs_row
= lines
;
1163 /* initial scrolling area is the whole window */
1164 t
->scroll_top
= lines
;
1165 t
->scroll_bot
= lines
+ rows
;
1166 if (scroll_buf_sz
< 0)
1168 t
->scroll_buf_sz
= scroll_buf_sz
;
1169 t
->scroll_buf
= scroll_buf
= calloc(scroll_buf_sz
, sizeof(Row
));
1170 for (Row
*row
= scroll_buf
, *end
= scroll_buf
+ scroll_buf_sz
; row
< end
; row
++)
1171 row
->cells
= calloc(cols
, sizeof(Cell
));
1174 Vt
*vt_create(int rows
, int cols
, int scroll_buf_sz
)
1178 if (rows
<= 0 || cols
<= 0)
1181 t
= calloc(1, sizeof(Vt
));
1186 t
->deffg
= t
->defbg
= -1;
1187 buffer_init(&t
->buffer_normal
, rows
, cols
, scroll_buf_sz
);
1188 t
->buffer
= &t
->buffer_normal
;
1192 void buffer_resize(Buffer
*t
, int rows
, int cols
)
1194 Row
*lines
= t
->lines
;
1196 if (t
->rows
!= rows
) {
1197 if (t
->curs_row
> lines
+ rows
) {
1198 /* scroll up instead of simply chopping off bottom */
1199 fill_scroll_buf(t
, (t
->curs_row
- t
->lines
) - rows
+ 1);
1201 while (t
->rows
> rows
) {
1202 free(lines
[t
->rows
- 1].cells
);
1206 lines
= realloc(lines
, sizeof(Row
) * rows
);
1209 if (t
->maxcols
< cols
) {
1210 for (int row
= 0; row
< t
->rows
; row
++) {
1211 lines
[row
].cells
= realloc(lines
[row
].cells
, sizeof(Cell
) * cols
);
1213 row_set(lines
+ row
, t
->cols
, cols
- t
->cols
, 0);
1214 lines
[row
].dirty
= true;
1216 Row
*sbuf
= t
->scroll_buf
;
1217 for (int row
= 0; row
< t
->scroll_buf_sz
; row
++) {
1218 sbuf
[row
].cells
= realloc(sbuf
[row
].cells
, sizeof(Cell
) * cols
);
1220 row_set(sbuf
+ row
, t
->cols
, cols
- t
->cols
, 0);
1224 } else if (t
->cols
!= cols
) {
1225 for (int row
= 0; row
< t
->rows
; row
++)
1226 lines
[row
].dirty
= true;
1231 if (t
->rows
< rows
) {
1232 while (t
->rows
< rows
) {
1233 lines
[t
->rows
].cells
= calloc(t
->maxcols
, sizeof(Cell
));
1234 row_set(lines
+ t
->rows
, 0, t
->maxcols
, t
);
1238 /* prepare for backfill */
1239 if (t
->curs_row
>= t
->scroll_bot
- 1) {
1240 deltarows
= t
->lines
+ rows
- t
->curs_row
- 1;
1241 if (deltarows
> t
->scroll_buf_len
)
1242 deltarows
= t
->scroll_buf_len
;
1246 t
->curs_row
+= lines
- t
->lines
;
1247 t
->scroll_top
= lines
;
1248 t
->scroll_bot
= lines
+ rows
;
1251 /* perform backfill */
1252 if (deltarows
> 0) {
1253 fill_scroll_buf(t
, -deltarows
);
1254 t
->curs_row
+= deltarows
;
1258 void vt_resize(Vt
*t
, int rows
, int cols
)
1260 struct winsize ws
= {.ws_row
= rows
,.ws_col
= cols
};
1262 if (rows
<= 0 || cols
<= 0)
1266 buffer_resize(t
->buffer
, rows
, cols
);
1267 clamp_cursor_to_bounds(t
);
1268 ioctl(t
->pty
, TIOCSWINSZ
, &ws
);
1269 kill(-t
->childpid
, SIGWINCH
);
1272 void buffer_free(Buffer
*t
)
1274 for (int i
= 0; i
< t
->rows
; i
++)
1275 free(t
->lines
[i
].cells
);
1277 for (int i
= 0; i
< t
->scroll_buf_sz
; i
++)
1278 free(t
->scroll_buf
[i
].cells
);
1279 free(t
->scroll_buf
);
1282 void vt_destroy(Vt
*t
)
1286 buffer_free(&t
->buffer_normal
);
1290 void vt_dirty(Vt
*vt
)
1292 Buffer
*t
= vt
->buffer
;
1293 for (Row
*row
= t
->lines
, *end
= row
+ t
->rows
; row
< end
; row
++)
1297 void vt_draw(Vt
*vt
, WINDOW
* win
, int srow
, int scol
)
1299 Buffer
*t
= vt
->buffer
;
1301 for (int i
= 0; i
< t
->rows
; i
++) {
1302 Row
*row
= t
->lines
+ i
;
1307 wmove(win
, srow
+ i
, scol
);
1309 for (int j
= 0; j
< t
->cols
; j
++) {
1310 Cell
*prev_cell
= cell
;
1311 cell
= row
->cells
+ j
;
1312 if (!prev_cell
|| cell
->attr
!= prev_cell
->attr
1313 || cell
->fg
!= prev_cell
->fg
1314 || cell
->bg
!= prev_cell
->bg
) {
1315 if (cell
->attr
== A_NORMAL
)
1316 cell
->attr
= vt
->defattrs
;
1318 cell
->fg
= vt
->deffg
;
1320 cell
->bg
= vt
->defbg
;
1321 wattrset(win
, (attr_t
) cell
->attr
<< NCURSES_ATTR_SHIFT
);
1322 wcolor_set(win
, vt_color_get(vt
, cell
->fg
, cell
->bg
), NULL
);
1324 if (is_utf8
&& cell
->text
>= 128) {
1325 char buf
[MB_CUR_MAX
+ 1];
1326 int len
= wcrtomb(buf
, cell
->text
, NULL
);
1327 waddnstr(win
, buf
, len
);
1328 if (wcwidth(cell
->text
) > 1)
1331 waddch(win
, cell
->text
> ' ' ? cell
->text
: ' ');
1337 wmove(win
, srow
+ t
->curs_row
- t
->lines
, scol
+ t
->curs_col
);
1338 curs_set(vt_cursor(vt
));
1341 void vt_scroll(Vt
*vt
, int rows
)
1343 Buffer
*t
= vt
->buffer
;
1344 if (rows
< 0) { /* scroll back */
1345 if (rows
< -t
->scroll_buf_len
)
1346 rows
= -t
->scroll_buf_len
;
1347 } else { /* scroll forward */
1348 if (rows
> t
->scroll_amount
)
1349 rows
= t
->scroll_amount
;
1351 fill_scroll_buf(t
, rows
);
1352 t
->scroll_amount
-= rows
;
1355 void vt_noscroll(Vt
*t
)
1357 int scroll_amount
= t
->buffer
->scroll_amount
;
1359 vt_scroll(t
, scroll_amount
);
1362 void vt_bell(Vt
*t
, bool bell
)
1367 void vt_togglebell(Vt
*t
)
1372 pid_t
vt_forkpty(Vt
*t
, const char *p
, const char *argv
[], const char *env
[], int *pty
)
1376 const char **envp
= env
;
1379 ws
.ws_row
= t
->buffer
->rows
;
1380 ws
.ws_col
= t
->buffer
->cols
;
1381 ws
.ws_xpixel
= ws
.ws_ypixel
= 0;
1383 pid
= forkpty(&t
->pty
, NULL
, NULL
, &ws
);
1390 maxfd
= sysconf(_SC_OPEN_MAX
);
1391 for (fd
= 3; fd
< maxfd
; fd
++)
1392 if (close(fd
) == -1 && errno
== EBADF
)
1395 while (envp
&& envp
[0]) {
1396 setenv(envp
[0], envp
[1], 1);
1399 setenv("TERM", COLORS
>= 256 ? "rxvt-256color" : "rxvt", 1);
1400 execv(p
, (char *const *)argv
);
1401 fprintf(stderr
, "\nexecv() failed.\nCommand: '%s'\n", argv
[0]);
1407 return t
->childpid
= pid
;
1410 int vt_getpty(Vt
*t
)
1415 int vt_write(Vt
*t
, const char *buf
, int len
)
1420 int res
= write(t
->pty
, buf
, len
);
1421 if (res
< 0 && errno
!= EAGAIN
&& errno
!= EINTR
)
1431 static void send_curs(Vt
*vt
)
1433 Buffer
*t
= vt
->buffer
;
1435 sprintf(keyseq
, "\e[%d;%dR", (int)(t
->curs_row
- t
->lines
), t
->curs_col
);
1436 vt_write(vt
, keyseq
, strlen(keyseq
));
1439 void vt_keypress(Vt
*t
, int keycode
)
1441 char c
= (char)keycode
;
1445 if (keycode
>= 0 && keycode
< KEY_MAX
&& keytable
[keycode
]) {
1451 char keyseq
[3] = { '\e', (t
->curskeymode
? 'O' : '['), keytable
[keycode
][0] };
1452 vt_write(t
, keyseq
, sizeof keyseq
);
1456 vt_write(t
, keytable
[keycode
], strlen(keytable
[keycode
]));
1463 void vt_mouse(Vt
*t
, int x
, int y
, mmask_t mask
)
1465 #ifdef NCURSES_MOUSE_VERSION
1466 char seq
[6] = { '\e', '[', 'M' }, state
= 0, button
= 0;
1471 if (mask
& (BUTTON1_PRESSED
| BUTTON1_CLICKED
))
1473 else if (mask
& (BUTTON2_PRESSED
| BUTTON2_CLICKED
))
1475 else if (mask
& (BUTTON3_PRESSED
| BUTTON3_CLICKED
))
1477 else if (mask
& (BUTTON1_RELEASED
| BUTTON2_RELEASED
| BUTTON3_RELEASED
))
1480 if (mask
& BUTTON_SHIFT
)
1482 if (mask
& BUTTON_ALT
)
1484 if (mask
& BUTTON_CTRL
)
1487 seq
[3] = 32 + button
+ state
;
1491 vt_write(t
, seq
, sizeof seq
);
1493 if (mask
& (BUTTON1_CLICKED
| BUTTON2_CLICKED
| BUTTON3_CLICKED
)) {
1494 /* send a button release event */
1496 seq
[3] = 32 + button
+ state
;
1497 vt_write(t
, seq
, sizeof seq
);
1499 #endif /* NCURSES_MOUSE_VERSION */
1502 static unsigned int color_hash(short fg
, short bg
)
1508 return fg
* (COLORS
+ 2) + bg
;
1511 short vt_color_get(Vt
*t
, short fg
, short bg
)
1514 fg
= (t
? t
->deffg
: default_fg
);
1516 bg
= (t
? t
->defbg
: default_bg
);
1518 if (!has_default_colors
) {
1520 fg
= (t
&& t
->deffg
!= -1 ? t
->deffg
: default_fg
);
1522 bg
= (t
&& t
->defbg
!= -1 ? t
->defbg
: default_bg
);
1525 if (!color2palette
|| (fg
== -1 && bg
== -1))
1527 unsigned int index
= color_hash(fg
, bg
);
1528 if (color2palette
[index
] == 0) {
1531 if (++color_pair_current
>= color_pairs_max
)
1532 color_pair_current
= color_pairs_reserved
+ 1;
1533 pair_content(color_pair_current
, &oldfg
, &oldbg
);
1534 unsigned int old_index
= color_hash(oldfg
, oldbg
);
1535 if (color2palette
[old_index
] >= 0) {
1536 if (init_pair(color_pair_current
, fg
, bg
) == OK
) {
1537 color2palette
[old_index
] = 0;
1538 color2palette
[index
] = color_pair_current
;
1545 short color_pair
= color2palette
[index
];
1546 return color_pair
>= 0 ? color_pair
: -color_pair
;
1549 short vt_color_reserve(short fg
, short bg
)
1551 if (!color2palette
|| fg
>= COLORS
|| bg
>= COLORS
)
1553 if (!has_default_colors
&& fg
== -1)
1555 if (!has_default_colors
&& bg
== -1)
1557 if (fg
== -1 && bg
== -1)
1559 unsigned int index
= color_hash(fg
, bg
);
1560 if (color2palette
[index
] >= 0) {
1561 if (init_pair(++color_pairs_reserved
, fg
, bg
) == OK
)
1562 color2palette
[index
] = -color_pairs_reserved
;
1564 short color_pair
= color2palette
[index
];
1565 return color_pair
>= 0 ? color_pair
: -color_pair
;
1568 static void init_colors(void)
1570 pair_content(0, &default_fg
, &default_bg
);
1571 if (default_fg
== -1)
1572 default_fg
= COLOR_WHITE
;
1573 if (default_bg
== -1)
1574 default_bg
= COLOR_BLACK
;
1575 has_default_colors
= (use_default_colors() == OK
);
1576 color_pairs_max
= MIN(COLOR_PAIRS
, MAX_COLOR_PAIRS
);
1577 color2palette
= calloc((COLORS
+ 2) * (COLORS
+ 2), sizeof(short));
1578 vt_color_reserve(COLOR_WHITE
, COLOR_BLACK
);
1587 void vt_shutdown(void)
1589 free(color2palette
);
1592 void vt_set_escseq_handler(Vt
*t
, vt_escseq_handler_t handler
)
1594 t
->escseq_handler
= handler
;
1597 void vt_set_event_handler(Vt
*t
, vt_event_handler_t handler
)
1599 t
->event_handler
= handler
;
1602 void vt_set_data(Vt
*t
, void *data
)
1607 void *vt_get_data(Vt
*t
)
1612 unsigned vt_cursor(Vt
*t
)
1614 return t
->buffer
->scroll_amount
? 0 : !t
->curshid
;