added "amber tint" mode for selection (default)
[yterm.git] / src / term_lib.inc.c
blob701bd1cd1b6171e389cc11ff796746d97836e9ed
1 /*
2 * YTerm -- (mostly) GNU/Linux X11 terminal emulator
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 various terminal procedures.
21 included directly into the main file.
24 #define HAS_DIRTY(tt_) \
25 ((tt_) != NULL \
26 ? (TERM_IN_SELECTION(tt_) ? hvbuf.dirtyCount : TERM_CBUF(tt_)->dirtyCount) \
27 : 0)
29 #define CALL_RENDER(code_) do { \
30 const yterm_bool oldgi = cbufInverse; \
31 const int oldcm = colorMode; \
32 CursorPos *rcpos; \
33 CellBuffer *rcbuf; \
34 CursorPos scpos; \
35 Region clipreg = x11_render_osd_menus(curr_menu); \
36 if (term->osd_messages != NULL) { \
37 cbufInverse = 0; \
38 colorMode = CMODE_NORMAL; \
39 clipreg = x11_render_osd(term->osd_messages->message, clipreg); \
40 } \
41 if (!TERM_IN_SELECTION(term)) { \
42 rcpos = (TERM_CURVISIBLE(term) ? TERM_CPOS(term) : NULL); \
43 rcbuf = TERM_CBUF(term); \
44 cbufInverse = !!(term->mode & YTERM_MODE_REVERSE); \
45 if (term->colorMode >= 0) colorMode = term->colorMode; \
46 } else { \
47 cbufInverse = 0; \
48 colorMode = (opt_amber_tint ? CMODE_AMBER_TINT : CMODE_AMBER); \
49 rcpos = &scpos; \
50 rcbuf = &hvbuf; \
51 scpos.x = term->history.cx; \
52 scpos.y = term->history.cy; \
53 } \
54 XSetRegion(x11_dpy, x11_gc, clipreg); \
55 XDestroyRegion(clipreg); /* we don't need it anymore */ \
56 code_ \
57 /*if (term->osd_messages != NULL || curr_menu != NULL)*/ { \
58 /* reset OSD clip mask */ \
59 XSetClipMask(x11_dpy, x11_gc, None); \
60 } \
61 cbufInverse = oldgi; \
62 colorMode = oldcm; \
63 } while (0)
66 //==========================================================================
68 // term_send_size_ioctl
70 //==========================================================================
71 static void term_send_size_ioctl (Term *term) {
72 if (term != NULL && term->child.cmdfd >= 0) {
73 struct winsize w;
74 w.ws_row = TERM_CBUF(term)->height;
75 w.ws_col = TERM_CBUF(term)->width;
76 w.ws_xpixel = 0;
77 w.ws_ypixel = 0;
78 if (ioctl(term->child.cmdfd, TIOCSWINSZ, &w) < 0) {
79 fprintf(stderr, "WARNING: couldn't set window size: %s\n", strerror(errno));
85 //==========================================================================
87 // term_reset_mode
89 //==========================================================================
90 static void term_reset_mode (Term *term) {
91 if (term != NULL) {
92 term->wkscr = &term->main; //k8: dunno
94 term->escseq.state = 0;
95 term->mode = YTERM_MODE_WRAP/*|YTERM_MODE_MOUSEBTN*//*|YTERM_MODE_GFX1*/;
96 term->mreport.mode = 1000;
97 term->mreport.lastx = -1;
98 term->mreport.lasty = -1;
99 term->charset = YTERM_MODE_GFX0;
101 term->main.curpos.x = 0; term->main.curpos.y = 0;
102 term->main.curpos.lastColFlag = 0;
103 term->main.curpos.justMoved = 0;
104 term->main.cursaved = term->main.curpos;
105 term->main.curhidden = 0;
106 term->main.currattr.ch = 0x20;
107 term->main.currattr.flags = 0;
108 term->main.currattr.fg = CELL_STD_COLOR(CELL_DEFAULT_ATTR);
109 term->main.currattr.bg = CELL_STD_COLOR(CELL_DEFAULT_ATTR);
110 term->main.scTop = 0; term->main.scBot = term->main.cbuf.height - 1;
111 term->main.attrsaved = term->main.currattr;
112 term->main.charsetsaved = 0;
113 term->main.modesaved = 0;
115 term->alt.curpos = term->main.curpos;
116 term->alt.cursaved = term->alt.curpos;
117 term->alt.currattr = term->main.currattr;
118 term->alt.curhidden = 0;
119 term->alt.scTop = 0; term->alt.scBot = term->alt.cbuf.height - 1;
120 term->alt.attrsaved = term->alt.currattr;
121 term->alt.charsetsaved = 0;
122 term->alt.modesaved = 0;
123 // free alt buffer; it will be recreated if necessary
124 cbuf_free(&term->alt.cbuf);
129 //==========================================================================
131 // term_init
133 // initialise term structure, create screens, etc.
135 //==========================================================================
136 static void term_init (Term *term, int w, int h) {
137 yterm_assert(term != NULL);
138 yterm_assert(w >= MinBufferWidth && w <= MaxBufferWidth);
139 yterm_assert(h >= MinBufferHeight && h <= MaxBufferHeight);
141 memset(term, 0, sizeof(Term));
143 cbuf_new(&term->main.cbuf, w, h);
144 //cbuf_new(&term->alt.cbuf, w, h);
145 term->wkscr = &term->main;
147 term->history.file.fd = -1;
148 term->history.enabled = opt_history_enabled;
150 term->active = 0;
151 term->colorMode = -1;
152 term->mouse_reports = -1;
153 term->escesc = -1;
155 term->child.pid = 0;
156 term->child.cmdfd = -1;
158 term->title.pgrp = 0;
159 snprintf(term->title.last, sizeof(term->title.last), "%s", opt_title);
160 term->title.custom = 0;
161 term->title.next_check_time = 0;
163 term->deadstate = DS_ALIVE;
165 term->wrbuf.buf = NULL;
166 term->wrbuf.size = 0;
167 term->wrbuf.used = 0;
169 term->rdcp = 0;
171 term->last_acttime = 0;
173 term_reset_mode(term);
175 term->prev = NULL;
176 term->next = NULL;
180 //==========================================================================
182 // term_wipe_osd_messages
184 //==========================================================================
185 static void term_wipe_osd_messages (Term *term) {
186 if (term != NULL) {
187 while (term->osd_messages != NULL) {
188 Message *msg = term->osd_messages;
189 term->osd_messages = msg->next;
190 free(msg->message);
196 //==========================================================================
198 // term_cleanup
200 // this WIPES the whole struct
202 //==========================================================================
203 static void term_cleanup (Term *term) {
204 if (term) {
205 cbuf_free(&term->main.cbuf);
206 cbuf_free(&term->alt.cbuf);
207 free(term->wrbuf.buf);
208 if (term->history.file.fd > 2) close(term->history.file.fd);
209 memset(term, 0, sizeof(Term));
210 term->history.file.fd = -1;
211 term->child.cmdfd = -1;
212 term_wipe_osd_messages(term);
217 //==========================================================================
219 // term_close_fd
221 //==========================================================================
222 static void term_close_fd (Term *term) {
223 if (term != NULL) {
224 if (term->child.cmdfd >= 0) {
225 close(term->child.cmdfd);
226 term->child.cmdfd = -1;
228 free(term->wrbuf.buf); term->wrbuf.buf = NULL;
229 term->wrbuf.size = 0; term->wrbuf.used = 0;
234 //==========================================================================
236 // term_kill_child
238 // kill child, close fd, free output buffer
240 //==========================================================================
241 static void term_kill_child (Term *term) {
242 if (term != NULL) {
243 if (term->child.pid != 0) {
244 kill(term->child.pid, SIGKILL);
245 term->child.pid = 0;
247 term_close_fd(term);
252 //==========================================================================
254 // term_osd_message
256 //==========================================================================
257 static void term_osd_message (Term *term, const char *text) {
258 if (term != NULL && term->active && term->deadstate != DS_DEAD &&
259 text != NULL && text[0] != 0)
261 const int timeout = 3000;
263 if (term->osd_messages != NULL) {
264 // invalidate top line (that's where OSDs are)
265 cbuf_mark_line_dirty(TERM_CBUF(term), 0);
266 term_wipe_osd_messages(term);
269 Message *msg = calloc(1, sizeof(Message));
270 if (msg == NULL) return;
271 msg->message = strdup(text);
272 if (msg->message == NULL) { free(msg); return; }
273 msg->next = NULL;
275 Message *last = term->osd_messages;
276 if (last == NULL) {
277 msg->time = yterm_get_msecs() + (unsigned)timeout;
278 term->osd_messages = msg;
279 } else {
280 msg->time = (unsigned)timeout;
281 while (last->next != NULL) last = last->next;
282 last->next = msg;
288 //==========================================================================
290 // term_invalidate_cursor
292 //==========================================================================
293 YTERM_STATIC_INLINE void term_invalidate_cursor (Term *term) {
294 if (term != NULL && TERM_CURVISIBLE(term)) {
295 const CursorPos *cpos = TERM_CPOS(term);
296 cbuf_mark_dirty(TERM_CBUF(term), cpos->x, cpos->y); // this will erase the old cursor
301 // ////////////////////////////////////////////////////////////////////////// //
302 enum {
303 SaveSimple = 0,
304 SaveDEC = 1,
305 SaveANSI = 2, // same as DEC for now
306 SaveXTerm = 3,
310 //==========================================================================
312 // term_save_cursor
314 //==========================================================================
315 static void term_save_cursor (Term *term, int fullmode) {
316 term->wkscr->cursaved = *TERM_CPOS(term);
317 if (fullmode != SaveSimple) {
318 term->wkscr->attrsaved = term->wkscr->currattr;
319 term->wkscr->charsetsaved = term->charset;
320 term->wkscr->modesaved = term->mode;
321 // also save autowrapping
322 if (fullmode == SaveXTerm) {
323 term->wkscr->modesaved |= (term->mode & YTERM_MODE_WRAP);
329 //==========================================================================
331 // term_restore_cursor
333 //==========================================================================
334 static void term_restore_cursor (Term *term, int fullmode) {
335 const int ox = TERM_CPOS(term)->x;
336 const int oy = TERM_CPOS(term)->y;
337 const int dorest = (term->wkscr->cursaved.x != ox || term->wkscr->cursaved.y != oy);
338 if (dorest) term_invalidate_cursor(term);
339 *TERM_CPOS(term) = term->wkscr->cursaved;
340 if (dorest) term_invalidate_cursor(term);
341 // restore other attrs
342 if (fullmode != SaveSimple) {
343 uint32_t mask = YTERM_MODE_GFX_MASK;
344 // also restore autowrapping
345 if (fullmode == SaveXTerm) mask |= YTERM_MODE_WRAP;
346 term->wkscr->currattr = term->wkscr->attrsaved;
347 term->mode = (term->mode & ~mask) | (term->wkscr->modesaved & mask);
348 term->charset = (term->charset & ~YTERM_MODE_GFX_MASK) |
349 (term->wkscr->charsetsaved & YTERM_MODE_GFX_MASK);
354 //==========================================================================
356 // sign
358 //==========================================================================
359 YTERM_STATIC_INLINE int sign (int v) {
360 return (v < 0 ? -1 : v > 0 ? +1 : 0);
364 //==========================================================================
366 // term_scroll_internal
368 // negative is up
370 //==========================================================================
371 static void term_scroll_internal (Term *term, int lines) {
372 if (term == NULL || lines == 0) return;
374 CellBuffer *cbuf = TERM_CBUF(term);
375 const int wdt = cbuf->width;
376 const int stop = TERM_SCTOP(term);
377 const int sbot = TERM_SCBOT(term);
379 const int dir = (lines < 0 ? -1 : +1);
380 const int regh = sbot - stop + 1;
381 lines = abs(lines); // it is safe, there will never be any overflow
382 if (lines >= regh) {
383 // the whole area is scrolled, just clear it
384 if (term->scroll_count != 0 && !term_scroll_locked(term)) {
385 // mark previous scroll region as dirty, why not
386 cbuf_mark_region_dirty(cbuf, 0, term->scroll_y0, cbuf->width - 1, term->scroll_y1);
388 cbuf_clear_region(cbuf, 0, stop, wdt - 1, sbot, TERM_CATTR(term));
389 term_lock_scroll(term);
390 } else {
391 // erase the cursor (because why not)
392 term_invalidate_cursor(term);
394 // if the window is invisible, required actions are taken by X event processor
395 yterm_bool do_copy = winVisible && term->active && !term_scroll_locked(term) &&
396 // for now, don't bother with scoll acceleration, if OSD is active
397 curr_menu == NULL && term->osd_messages == NULL;
399 // can accumulate more scroll?
400 if (do_copy) {
401 if (term->scroll_count == 0) {
402 // first scroll, remember the area
403 term->scroll_y0 = stop;
404 term->scroll_y1 = sbot;
405 term->scroll_count = dir * lines;
406 } else if (sign(term->scroll_count) != dir || abs(term->scroll_count) + lines >= regh ||
407 term->scroll_y0 != stop || term->scroll_y1 != sbot)
409 // the whole region is scrolled away, or different direction, or different scroll area
410 // dirty only previously scrolled area
411 cbuf_mark_region_dirty(cbuf, 0, term->scroll_y0, cbuf->width - 1, term->scroll_y1);
412 term_lock_scroll(term);
413 do_copy = 0;
414 } else {
415 // accumulate
416 yterm_assert(sign(term->scroll_count) == dir);
417 term->scroll_count += dir * lines;
419 } else if (term->scroll_count != 0 && !term_scroll_locked(term)) {
420 // cannot copy anymore: dirty previously scrolled area
421 cbuf_mark_region_dirty(cbuf, 0, term->scroll_y0, cbuf->width - 1, term->scroll_y1);
422 term_lock_scroll(term);
425 // scroll it
426 if (dir < 0) {
427 // up
428 cbuf_scroll_area_up(cbuf, stop, sbot, lines,
429 (do_copy ? CBUF_DIRTY_OVERWRITE : CBUF_DIRTY_MERGE),
430 TERM_CATTR(term));
431 } else {
432 // down
433 cbuf_scroll_area_down(cbuf, stop, sbot, lines,
434 (do_copy ? CBUF_DIRTY_OVERWRITE : CBUF_DIRTY_MERGE),
435 TERM_CATTR(term));
438 // put the cursor back (because why not)
439 term_invalidate_cursor(term);
444 //==========================================================================
446 // term_scroll_up
448 // called either for normal scroll, or for automatic scroll
450 //==========================================================================
451 YTERM_STATIC_INLINE void term_scroll_up (Term *term, int lines) {
452 if (term != NULL && lines > 0) {
453 // write to history?
454 if (lines == 1 && term->history.enabled && term->wkscr == &term->main &&
455 term->wkscr->scTop == 0)
457 // top line will be scrolled away, save it to history file
458 history_append_line(TERM_CBUF(term), 0, &term->history.file);
460 term_scroll_internal(term, -lines);
465 //==========================================================================
467 // term_scroll_down
469 //==========================================================================
470 YTERM_STATIC_INLINE void term_scroll_down (Term *term, int lines) {
471 if (term != NULL && lines > 0) {
472 term_scroll_internal(term, lines);
477 //==========================================================================
479 // term_lcf_do
481 //==========================================================================
482 YTERM_STATIC_INLINE void term_lcf_do (Term *term) {
483 CursorPos *cpos = TERM_CPOS(term);
484 if (cpos->lastColFlag != 0) {
485 yterm_assert(cpos->x == TERM_CBUF(term)->width - 1);
486 cpos->lastColFlag = 0;
487 cpos->justMoved = 0;
488 // mark this line as autowrapped
489 cbuf_mark_line_autowrap(TERM_CBUF(term), cpos->y);
490 // move curosor to the next line
491 term_invalidate_cursor(term);
492 cpos->x = 0;
493 if (cpos->y == TERM_SCBOT(term)) {
494 term_scroll_up(term, 1);
495 } else if (cpos->y != TERM_CBUF(term)->height - 1) {
496 cpos->y += 1;
497 term_invalidate_cursor(term);
503 //==========================================================================
505 // term_lcf_reset
507 //==========================================================================
508 YTERM_STATIC_INLINE void term_lcf_reset (Term *term) {
509 TERM_CPOS(term)->lastColFlag = 0;
510 TERM_CPOS(term)->justMoved = 1;
514 //==========================================================================
516 // term_lcf_reset_nocmove
518 //==========================================================================
519 YTERM_STATIC_INLINE void term_lcf_reset_nocmove (Term *term) {
520 TERM_CPOS(term)->lastColFlag = 0;
524 //==========================================================================
526 // term_curmove_abs
528 //==========================================================================
529 static void term_curmove_abs (Term *term, int nx, int ny) {
530 if (term != NULL) {
531 term_lcf_reset(term);
532 CursorPos *cpos = TERM_CPOS(term);
533 nx = clamp(nx, 0, TERM_CBUF(term)->width - 1);
534 if ((term->mode & YTERM_MODE_ORIGIN) != 0) {
535 ny = clamp(ny, 0, TERM_CBUF(term)->height - 1); // avoid overflows
536 ny = min2(ny + TERM_SCTOP(term), TERM_SCBOT(term));
537 } else {
538 ny = clamp(ny, 0, TERM_CBUF(term)->height - 1);
540 if (nx != cpos->x || ny != cpos->y) {
541 term_invalidate_cursor(term);
542 cpos->x = nx; cpos->y = ny;
543 cpos->justMoved = 1;
544 term_invalidate_cursor(term);
550 //==========================================================================
552 // term_curmove_rel
554 //==========================================================================
555 static void term_curmove_rel (Term *term, int dx, int dy) {
556 if (term != NULL) {
557 term_lcf_reset(term);
558 CursorPos *cpos = TERM_CPOS(term);
559 dx = clamp(dx, -32767, 32767);
560 dy = clamp(dy, -32767, 32767);
561 int nx = clamp(cpos->x + dx, 0, TERM_CBUF(term)->width - 1);
562 int ny = cpos->y;
563 if ((term->mode & YTERM_MODE_ORIGIN) != 0) {
564 // i believe that it is first clamped to the region, and then moved
565 // it may be totally wrong, thoug
566 ny = clamp(ny, TERM_SCTOP(term), TERM_SCBOT(term));
567 ny = clamp(ny + dy, TERM_SCTOP(term), TERM_SCBOT(term));
568 } else {
569 ny = clamp(ny + dy, 0, TERM_CBUF(term)->height - 1);
571 if (nx != cpos->x || ny != cpos->y) {
572 term_invalidate_cursor(term);
573 cpos->x = nx; cpos->y = ny;
574 cpos->justMoved = 1;
575 term_invalidate_cursor(term);
581 static const uint16_t term_font_gfx_table[32] = {
582 0x25c6, 0x2592, 0x0062, 0x0063, 0x0064, 0x0065, 0x00b0, 0x00b1, // 0x60..0x67
583 0x0068, 0x0069, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x006f, // 0x68..0x6f
584 0x0070, 0x2500, 0x0072, 0x0073, 0x251c, 0x2524, 0x2534, 0x252c, // 0x70..0x77
585 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x0020, // 0x78..0x7f
589 //==========================================================================
591 // term_putc
593 // write unicode char to terminal, advance cursor
594 // correcly processes some control chars (BS, CR, LF, TAB)
596 //==========================================================================
597 static void term_putc (Term *term, uint32_t cp) {
598 if (term == NULL) return;
600 CursorPos *cpos = TERM_CPOS(term);
601 CellBuffer *cbuf = TERM_CBUF(term);
603 if (cp >= 0x20) {
604 // normal char
605 if ((term->mode & term->charset) != 0 && cp >= 0x60 && cp < 0x7f) {
606 // gfx charset
607 if (opt_terminus_gfx != 0) {
608 cp -= 0x5f;
609 } else {
610 cp = term_font_gfx_table[cp - 0x60];
613 if (cp == 0x7f) return; //k8: dunno
614 if (cpos->justMoved) {
615 cpos->justMoved = 0;
616 cbuf_unmark_line_autowrap(cbuf, cpos->y);
618 // scroll, and write
619 //const yterm_bool oldlcf = TERM_CPOS(term)->lastColFlag;
620 //if (oldlcf) term_erase_cursor(term);
621 term_lcf_do(term);
622 // insert mode?
623 if ((term->mode & YTERM_MODE_INSERT) != 0) {
624 cbuf_insert_chars(cbuf, cpos->x, cpos->y, 1, NULL);
626 cbuf_write_wchar(cbuf, cpos->x, cpos->y, cp, TERM_CATTR(term));
627 // advance cursor
628 if ((term->mode & YTERM_MODE_WRAP) != 0) {
629 // wrapping enabled
630 if (cpos->x == cbuf->width - 1) {
631 yterm_assert(cpos->lastColFlag == 0);
632 cpos->lastColFlag = 1;
633 cbuf_unmark_line_autowrap(cbuf, cpos->y);
634 } else {
635 term_invalidate_cursor(term);
636 cpos->x += 1;
637 term_invalidate_cursor(term);
639 } else {
640 // wrapping disabled
641 if (cpos->x != cbuf->width - 1) {
642 term_invalidate_cursor(term);
643 cpos->x += 1;
644 term_invalidate_cursor(term);
645 } else {
646 cbuf_unmark_line_autowrap(cbuf, cpos->y);
649 //if (oldlcf) term_draw_cursor(term);
650 } else {
651 int nx;
652 // control char
653 switch (cp) {
654 case 0x07: // BELL
655 if (term->active) {
656 // do not spam with bells
657 uint64_t ctt = yterm_get_msecs();
658 if (ctt - lastBellTime >= 100) {
659 lastBellTime = ctt;
660 x11_bell();
661 if (!winFocused || !winVisible) {
662 x11_set_urgency(1);
666 break;
667 case 0x08: // BS
669 if (cpos->lastColFlag != 0) {
670 cpos->lastColFlag = 0;
671 term_lcf_reset(term); // indicate cursor movement
672 } else
675 term_lcf_reset(term);
676 if (cpos->x != 0)
677 if (cpos->x != 0 || cpos->y != 0) {
678 term_invalidate_cursor(term);
679 if (cpos->x == 0) {
680 cpos->x = cbuf->width - 1;
681 cpos->y -= 1;
682 } else {
683 cpos->x -= 1;
685 term_invalidate_cursor(term);
688 break;
689 case 0x09: // TAB
690 // i'm pretty sure that it works like this
691 // at least, this is what makes vttest happy ;-)
692 term_lcf_reset(term);
693 nx = min2((cpos->x | 7) + 1, cbuf->width - 1);
694 if (nx != cpos->x) {
695 term_invalidate_cursor(term);
696 cpos->x = nx;
697 term_invalidate_cursor(term);
699 break;
700 case 0x0A: // LF
701 case 0x0B: // VT
702 case 0x0C: // FF
703 term_lcf_reset(term);
704 cbuf_unmark_line_autowrap(cbuf, cpos->y);
705 if (cpos->y == TERM_SCBOT(term)) {
706 // at the bottom of the scroll area, just scroll
707 term_scroll_up(term, 1);
708 if ((term->mode & YTERM_MODE_CRLF) != 0 && cpos->x != 0) {
709 cpos->x = 0;
710 term_invalidate_cursor(term);
712 } else {
713 term_invalidate_cursor(term);
714 if ((term->mode & YTERM_MODE_CRLF) != 0) cpos->x = 0;
715 if (cpos->y != cbuf->height - 1) cpos->y += 1;
716 term_invalidate_cursor(term);
718 break;
719 case 0x0D: // CR
720 term_lcf_reset(term);
721 cbuf_unmark_line_autowrap(cbuf, cpos->y);
722 if (cpos->x != 0) {
723 term_invalidate_cursor(term);
724 cpos->x = 0;
725 term_invalidate_cursor(term);
727 break;
728 case 0x1A: // SUB: according to "wraptest", this should clear the flag too
729 term_lcf_reset_nocmove(term);
730 break;
736 //==========================================================================
738 // term_draw_info_window
740 // draw utf-8 info window with the given message
741 // used to notify about sudden processe death
743 //==========================================================================
744 static void term_draw_info_window (Term *term, const char *msg) {
745 if (msg == NULL || msg[0] == 0) return;
747 // this will hide the cursor
748 term_invalidate_cursor(term);
750 // reset terminal attributes
751 term->escseq.state = 0;
752 term->mode = 0/*YTERM_MODE_WRAP*/; // do not wrap text
753 term->charset = YTERM_MODE_GFX0;
755 const int wdt = TERM_CBUF(term)->width - 2;
757 TERM_CPOS(term)->x = 0; TERM_CPOS(term)->y = 0;
758 TERM_CPOS(term)->lastColFlag = 0;
759 term->wkscr->curhidden = 1;
760 TERM_CATTR(term)->flags = 0;
761 TERM_SCTOP(term) = 0; TERM_SCBOT(term) = TERM_CBUF(term)->height - 1;
764 // draw top window frame
765 TERM_CATTR(term)->fg = 0xffffff;
766 TERM_CATTR(term)->bg = 0xaa0000;
767 cbuf_write_wchar(TERM_CBUF(term), 0, 0, 0x259b, TERM_CATTR(term));
768 cbuf_write_wchar_count(TERM_CBUF(term), 1, 0, 0x2580, wdt, TERM_CATTR(term));
769 cbuf_write_wchar(TERM_CBUF(term), wdt + 1, 0, 0x259c, TERM_CATTR(term));
771 // draw window contents
772 TERM_CATTR(term)->fg = 0xaa0000;
773 TERM_CATTR(term)->bg = 0xffffff;
774 cbuf_write_wchar(TERM_CBUF(term), 0, 1, 0x2590, TERM_CATTR(term));
775 TERM_CATTR(term)->fg = 0xffffff;
776 TERM_CATTR(term)->bg = 0xaa0000;
777 cbuf_write_wchar_count(TERM_CBUF(term), 1, 1, ' ', wdt, TERM_CATTR(term));
778 cbuf_write_wchar(TERM_CBUF(term), wdt + 1, 1, 0x2590, TERM_CATTR(term));
780 // draw bottom window frame
781 cbuf_write_wchar(TERM_CBUF(term), 0, 2, 0x2599, TERM_CATTR(term));
782 TERM_CATTR(term)->fg = 0xaa0000;
783 TERM_CATTR(term)->bg = 0xffffff;
784 cbuf_write_wchar_count(TERM_CBUF(term), 1, 2, 0x2580, wdt, TERM_CATTR(term));
785 TERM_CATTR(term)->fg = 0xffffff;
786 TERM_CATTR(term)->bg = 0xaa0000;
787 cbuf_write_wchar(TERM_CBUF(term), wdt + 1, 2, 0x259f, TERM_CATTR(term));
789 TERM_CATTR(term)->fg = 0xffff00;
790 uint32_t cp = 0;
791 int x = 1;
792 while (*msg && x < wdt) {
793 cp = yterm_utf8d_consume(cp, *msg); msg += 1;
794 if (yterm_utf8_valid_cp(cp)) {
795 cbuf_write_wchar(TERM_CBUF(term), x, 1, cp, TERM_CATTR(term));
796 x += 1;
802 //==========================================================================
804 // term_fix_size
806 //==========================================================================
807 static yterm_bool term_fix_size (Term *term) {
808 if (term == NULL) return 0;
810 const int neww = clamp(winWidth / charWidth, 1, MaxBufferWidth);
811 const int newh = clamp(winHeight / charHeight, 1, MaxBufferHeight);
812 #if 0
813 fprintf(stderr, "RSZ: oldw=%d; oldh=%d; neww=%d; newh=%d; v=%d; m=%d\n",
814 cbuf->width, cbuf->height, neww, newh,
815 winVisible, winMapped);
816 #endif
818 if (neww != TERM_CBUF(term)->width || newh != TERM_CBUF(term)->height) {
819 #if 0
820 fprintf(stderr, "NEW TERM SIZE: %dx%d\n", neww, newh);
821 #endif
823 // reset selection mode for this terminal
824 term->history.blocktype = SBLOCK_NONE;
825 if (term->active) cbuf_free(&hvbuf);
827 term->scroll_count = 0;
829 // resize main screen
830 CursorPos *cpos = &term->main.curpos;
831 yterm_bool atbot = cpos->y >= term->main.cbuf.width;
832 if (cpos->x >= neww) cpos->x = neww - 1;
833 if (atbot) cpos->y = newh - 1; else if (cpos->y >= newh) cpos->y = newh - 1;
834 cbuf_resize(&term->main.cbuf, neww, newh, 1); /* relayout it */
835 // reset scroll area
836 term->main.scTop = 0; term->main.scBot = newh - 1;
838 // resize alt screen
839 cpos = &term->alt.curpos;
840 atbot = cpos->y >= term->alt.cbuf.width;
841 if (cpos->x >= neww) cpos->x = neww - 1;
842 if (atbot) cpos->y = newh - 1; else if (cpos->y >= newh) cpos->y = newh - 1;
843 if (term->alt.cbuf.width != 0 || term->wkscr == &term->alt) {
844 cbuf_resize(&term->alt.cbuf, neww, newh, 0); /* do not relayout it */
845 } else {
846 cbuf_free(&term->alt.cbuf);
848 // reset scroll area
849 term->alt.scTop = 0; term->alt.scBot = newh - 1;
851 term->main.curpos.lastColFlag = 0;
852 term->main.curpos.justMoved = 0;
854 term->alt.curpos.lastColFlag = 0;
855 term->alt.curpos.justMoved = 0;
857 term_send_size_ioctl(term);
859 #if 0
860 fprintf(stderr, "NEW REAL TERM SIZE: %dx%d\n",
861 term->wkscr->cbuf.width, term->wkscr->cbuf.height);
862 #endif
864 return 1;
867 return 0;
871 //==========================================================================
873 // term_can_read
875 //==========================================================================
876 static yterm_bool term_can_read (Term *term) {
877 if (term != NULL && term->deadstate == DS_ALIVE && term->child.cmdfd >= 0) {
878 fd_set rfd;
879 for (;;) {
880 struct timeval timeout = {0};
881 FD_ZERO(&rfd);
882 FD_SET(term->child.cmdfd, &rfd);
883 const int sres = select(term->child.cmdfd + 1, &rfd, NULL, NULL, &timeout);
884 if (sres < 0) {
885 if (errno == EINTR) continue;
886 //k8t_die("select failed: %s", strerror(errno));
887 return 0;
888 } else {
889 return (sres > 0 /*&& FD_ISSET(term->cmdfd, &rfd)*/ ? 1 : 0);
892 } else {
893 return 0;
898 //==========================================================================
900 // term_can_write
902 //==========================================================================
903 static yterm_bool term_can_write (Term *term) {
904 if (term != NULL && term->deadstate == DS_ALIVE && term->child.cmdfd >= 0) {
905 fd_set wfd;
906 for (;;) {
907 struct timeval timeout = {0};
908 FD_ZERO(&wfd);
909 FD_SET(term->child.cmdfd, &wfd);
910 const int sres = select(term->child.cmdfd + 1, NULL, &wfd, NULL, &timeout);
911 if (sres < 0) {
912 if (errno == EINTR) continue;
913 return 0;
914 } else {
915 return (sres > 0 /*&& FD_ISSET(term->child.cmdfd, &wfd)*/ ? 1 : 0);
918 } else {
919 return 0;
924 //==========================================================================
926 // term_write_raw
928 //==========================================================================
929 static void term_write_raw (Term *term, const void *buf, int buflen) {
930 if (buf != NULL && buflen != 0 && term != NULL &&
931 term->deadstate == DS_ALIVE && term->child.cmdfd >= 0)
933 if (buflen < 0) buflen = (int)strlen((const char *)buf);
934 if (term->wrbuf.used + (uint32_t)buflen > term->wrbuf.size) {
935 // grow buffer
936 if (term->wrbuf.size >= 0x00200000U ||
937 0x00200000U - term->wrbuf.size < (uint32_t)buflen)
939 fprintf(stderr, "ERROR: terminal output buffer overflow!\n");
940 term_close_fd(term);
941 return;
943 uint32_t newsz;
944 if (term->wrbuf.used + (uint32_t)buflen <= WRITE_BUF_SIZE) {
945 newsz = WRITE_BUF_SIZE;
946 } else {
947 newsz = ((term->wrbuf.used + (uint32_t)buflen) | 4095) + 1;
949 char *nbuf = realloc(term->wrbuf.buf, newsz);
950 if (nbuf == NULL) {
951 fprintf(stderr, "ERROR: out of memory for terminal output buffer!\n");
952 term_close_fd(term);
953 return;
955 #ifdef DUMP_WRITE_GROW
956 fprintf(stderr, "GROWING: %u -> %u (used: %u)\n",
957 term->wrbuf.size, newsz, term->wrbuf.used);
958 #endif
959 term->wrbuf.buf = nbuf;
960 term->wrbuf.size = newsz;
962 memcpy(term->wrbuf.buf + term->wrbuf.used, buf, (uint32_t)buflen);
963 term->wrbuf.used += (uint32_t)buflen;
965 if (term->deadstate != DS_ALIVE) {
966 term_close_fd(term);
971 //==========================================================================
973 // term_write
975 //==========================================================================
976 static void term_write (Term *term, const char *str) {
977 if (str && str[0] && term != NULL && term->deadstate == DS_ALIVE && term->child.cmdfd >= 0) {
978 if (koiLocale) {
979 // utf -> koi
980 const char *end = str;
981 while (*end > 0 && *end < 127) end += 1;
982 // write what we have
983 if (end != str) {
984 term_write_raw(term, str, (int)(ptrdiff_t)(end - str));
985 str = end;
987 // decode rest
988 char dcbuf[64];
989 int dcpos = 0;
990 uint32_t cp = 0;
991 while (*str) {
992 cp = yterm_utf8d_consume(cp, *str); str += 1;
993 if (yterm_utf8_valid_cp(cp)) {
994 char koi = yterm_uni2koi(cp);
995 if (koi) {
996 if (dcpos == (int)sizeof(dcbuf)) {
997 term_write_raw(term, dcbuf, dcpos);
998 dcpos = 0;
1000 dcbuf[dcpos] = koi;
1001 dcpos += 1;
1005 if (dcpos != 0) term_write_raw(term, dcbuf, dcpos);
1006 } else {
1007 term_write_raw(term, str, -1);
1013 //==========================================================================
1015 // term_write_sanitize
1017 // sanisizes unicode
1019 //==========================================================================
1020 static void term_write_sanitize (Term *term, const char *str, int slen) {
1021 if (str && str[0] && slen > 0 &&
1022 term != NULL && term->deadstate == DS_ALIVE && term->child.cmdfd >= 0)
1024 char utx[4];
1025 uint32_t cp = 0;
1026 while (slen > 0 && term->wrbuf.used < 1024*1024*32) { // arbitrary limit
1027 // try to flush the buffer
1028 if (term->wrbuf.used > 65535) {
1029 #if 0
1030 fprintf(stderr, "...trying to send %u bytes...\n", term->wrbuf.used);
1031 #endif
1032 one_term_write(term);
1034 cp = yterm_utf8d_consume(cp, *str); str += 1; slen -= 1;
1035 if (yterm_utf8_valid_cp(cp) && cp != 0 && cp != 127) {
1036 if (!yterm_utf8_printable_cp(cp)) cp = YTERM_UTF8_REPLACEMENT_CP;
1037 else if (cp < 32 && cp != '\t' && cp != '\r' && cp != '\n') cp = YTERM_UTF8_REPLACEMENT_CP;
1038 if (koiLocale) {
1039 // utf -> koi
1040 char koi = yterm_uni2koi(cp);
1041 if (koi > 0 && koi < 32 && koi != '\t' && koi != '\r' && koi != '\n') koi = 0;
1042 if (koi && koi != 127) term_write_raw(term, &koi, 1);
1043 } else {
1044 const uint32_t ulen = yterm_utf8_encode(utx, cp);
1045 term_write_raw(term, utx, (int)ulen);
1053 //==========================================================================
1055 // term_send_focus_event
1057 //==========================================================================
1058 static void term_send_focus_event (Term *term, yterm_bool focused) {
1059 if (term != NULL && (term ->mode & YTERM_MODE_FOCUSEVT) != 0) {
1060 if (focused) {
1061 term_write_raw(term, "\x1b[I", -1);
1062 } else {
1063 term_write_raw(term, "\x1b[O", -1);
1069 //==========================================================================
1071 // term_send_mouse
1073 //==========================================================================
1074 static void term_send_mouse (Term *term, int x, int y, int event, int button, int estate) {
1075 if (term != NULL && term->deadstate == DS_ALIVE && term->child.cmdfd >= 0 &&
1076 (term->mode & YTERM_MODE_MOUSE) != 0 &&
1077 x >= 0 && y >= 0 && x < TERM_CBUF(term)->width && y < TERM_CBUF(term)->height)
1079 char buf[32];
1080 char *p;
1081 char lastCh = 'M';
1082 int ss;
1084 /* modes:
1085 1000 -- X11 xterm mouse reporting
1086 1005 -- utf-8 mouse encoding
1087 1006 -- sgr mouse encoding
1088 1015 -- urxvt mouse encoding
1090 //sprintf(buf, "\x1b[M%c%c%c", 0, 32+x+1, 32+y+1);
1091 snprintf(buf, sizeof(buf), "\x1b[M");
1092 p = buf + strlen(buf);
1094 if (event == YTERM_MR_MOTION) {
1095 if ((term->mode & YTERM_MODE_MOUSEMOTION) != 0 &&
1096 (x != term->mreport.lastx || y != term->mreport.lasty))
1098 button = term->mreport.lastbtn + 32;
1099 term->mreport.lastx = x;
1100 term->mreport.lasty = y;
1102 } else if (event == YTERM_MR_UP) {
1103 if (term->mreport.mode != 1006) {
1104 button = 3; // 'release' flag
1105 } else {
1106 lastCh = 'm';
1107 button -= YTERM_MR_LEFT;
1108 if (button >= 3) button += 64 - 3;
1110 } else {
1111 yterm_assert(event == YTERM_MR_DOWN);
1112 button -= YTERM_MR_LEFT;
1113 if (button >= 3) button += 64 - 3;
1114 term->mreport.lastbtn = button;
1115 term->mreport.lastx = x;
1116 term->mreport.lasty = y;
1119 ss =
1120 (estate&Mod_Shift ? 4 : 0) |
1121 (estate&Mod_Alt ? 8 : 0) |
1122 (estate&Mod_Ctrl ? 16 : 0);
1124 switch (term->mreport.mode) {
1125 case 1006: /* sgr */
1126 p[-1] = '<';
1127 sprintf(p, "%d;", button+ss);
1128 p += strlen(p);
1129 break;
1130 case 1015: /* urxvt */
1131 p -= 1; // remove 'M'
1132 sprintf(p, "%d;", 32 + button + ss);
1133 p += strlen(p);
1134 break;
1135 default:
1136 *p = 32 + button + ss;
1137 p += 1;
1138 break;
1141 // coords
1142 switch (term->mreport.mode) {
1143 case 1005: /* utf-8 */
1144 p += yterm_utf8_encode(p, (uint32_t)x + 1);
1145 p += yterm_utf8_encode(p, (uint32_t)y + 1);
1146 break;
1147 case 1006: /* sgr */
1148 sprintf(p, "%d;%d%c", x + 1, y + 1, lastCh);
1149 p += strlen(p);
1150 break;
1151 case 1015: /* urxvt */
1152 sprintf(p, "%d;%dM", x + 1, y + 1);
1153 p += strlen(p);
1154 break;
1155 default:
1156 sprintf(p, "%c%c", 32 + x + 1, 32 + y + 1);
1157 p += strlen(p);
1158 break;
1160 *p = 0;
1162 term_write_raw(term, buf, -1);
1167 // ////////////////////////////////////////////////////////////////////////// //
1168 enum {
1169 YTERM_ESC_NONE,
1170 YTERM_ESC_ESC, // seen ESC
1172 YTERM_ESC_CSI,
1173 YTERM_ESC_CSI_IGNORE,
1175 YTERM_ESC_ALT_CHARSET_G0,
1176 YTERM_ESC_ALT_CHARSET_G1,
1177 YTERM_ESC_HASH,
1178 YTERM_ESC_PERCENT,
1180 YTERM_ESC_OSC,
1181 YTERM_ESC_DCS,
1183 YTERM_ESC_S_IGN, // string, ignore
1187 //==========================================================================
1189 // reset_esc
1191 // should touch only state!
1193 //==========================================================================
1194 YTERM_STATIC_INLINE void reset_esc (Term *term) {
1195 term->escseq.state = YTERM_ESC_NONE;
1199 //==========================================================================
1201 // start_csi
1203 // prepare to parse CSI sequense
1205 //==========================================================================
1206 YTERM_STATIC_INLINE void start_csi (Term *term) {
1207 term->escseq.state = YTERM_ESC_CSI;
1208 term->escseq.argc = 0;
1209 term->escseq.priv = 0;
1210 term->escseq.cmd = 0;
1214 //==========================================================================
1216 // start_osc
1218 //==========================================================================
1219 YTERM_STATIC_INLINE void start_osc (Term *term) {
1220 term->escseq.state = YTERM_ESC_OSC;
1221 term->escseq.sbufpos = 0;
1222 term->escseq.cmd = 0;
1223 term->escseq.priv = 0; // accum digits
1224 term->escseq.sbuf[0] = 0; // just in case
1228 //==========================================================================
1230 // start_dcs
1232 //==========================================================================
1233 YTERM_STATIC_INLINE void start_dcs (Term *term) {
1234 term->escseq.state = YTERM_ESC_DCS;
1235 term->escseq.sbufpos = 0;
1236 term->escseq.cmd = 0;
1237 term->escseq.priv = 0; // accum digits
1238 term->escseq.sbuf[0] = 0; // just in case
1242 //==========================================================================
1244 // finish_osc
1246 //==========================================================================
1247 static void finish_osc (Term *term) {
1248 if (term->escseq.state == YTERM_ESC_OSC) {
1249 if (term->escseq.cmd == 2) {
1250 char *ntt = term->escseq.sbuf;
1251 for (unsigned char *tmp = (unsigned char *)ntt; *tmp; tmp += 1) {
1252 if (*tmp < 32 || *tmp == 127) *tmp = ' ';
1254 // remove trailing spaces
1255 size_t nlen = strlen(ntt);
1256 while (nlen > 0 && ((unsigned char *)ntt)[nlen - 1] == 32) --nlen;
1257 ntt[nlen] = 0;
1258 // remove leading spaces
1259 nlen = 0;
1260 while (ntt[nlen] && ((unsigned char *)ntt)[nlen] == 32) nlen += 1;
1261 if (nlen != 0) memmove(ntt, ntt + nlen, strlen(ntt + nlen) + 1);
1262 if (ntt[0] == 0) {
1263 // restore default title
1264 term->title.custom = 0;
1265 snprintf(term->title.last, sizeof(term->title.last), "%s", opt_title);
1266 term_check_title(term);
1267 if (winMapped && term->active) {
1268 x11_change_title(term->title.last);
1270 } else {
1271 // set new custom title
1272 term_check_pgrp(term, 0);
1273 term->title.custom = 1;
1274 strcpy(term->title.last, ntt);
1275 if (winMapped && term->active) {
1276 x11_change_title(ntt);
1279 force_tab_redraw();
1285 #include "term_lib_csi.inc.c"
1288 //==========================================================================
1290 // term_process_altg0
1292 //==========================================================================
1293 static void term_process_altg0(Term *term, uint32_t cp) {
1294 if (opt_dump_esc_enabled) {
1295 fprintf(stderr, "ESC-DUMP: ESC-G0 %c (%u)\n", (cp > 32 && cp < 127 ? (char)cp : '.'), cp);
1297 reset_esc(term);
1298 switch (cp) {
1299 case '0': /* Line drawing crap */
1300 term->mode |= YTERM_MODE_GFX0;
1301 break;
1302 case 'B': /* Back to regular text */
1303 case 'U': /* character ROM */
1304 case 'K': /* user mapping */
1305 term->mode &= ~YTERM_MODE_GFX0;
1306 break;
1307 default:
1308 fprintf(stderr, "esc unhandled charset: ESC ( %c\n",
1309 (cp > 0x20 && cp < 0x7f ? (char)cp : '.'));
1310 term->mode &= ~YTERM_MODE_GFX0;
1311 break;
1316 //==========================================================================
1318 // term_process_altg1
1320 //==========================================================================
1321 static void term_process_altg1 (Term *term, uint32_t cp) {
1322 if (opt_dump_esc_enabled) {
1323 fprintf(stderr, "ESC-DUMP: ESC-G1 %c (%u)\n", (cp > 32 && cp < 127 ? (char)cp : '.'), cp);
1325 reset_esc(term);
1326 switch (cp) {
1327 case '0': /* Line drawing crap */
1328 term->mode |= YTERM_MODE_GFX1;
1329 break;
1330 case 'B': /* Back to regular text */
1331 case 'U': /* character ROM */
1332 case 'K': /* user mapping */
1333 term->mode &= ~YTERM_MODE_GFX1;
1334 break;
1335 default:
1336 fprintf(stderr, "esc unhandled charset: ESC ) %c\n",
1337 (cp > 0x20 && cp < 0x7f ? (char)cp : '.'));
1338 term->mode &= ~YTERM_MODE_GFX1;
1339 break;
1344 //==========================================================================
1346 // term_process_percent
1348 //==========================================================================
1349 static void term_process_percent (Term *term, uint32_t cp) {
1350 if (opt_dump_esc_enabled) {
1351 fprintf(stderr, "ESC-DUMP: ESC-%% %c (%u)\n", (cp > 32 && cp < 127 ? (char)cp : '.'), cp);
1353 reset_esc(term);
1354 switch (cp) {
1355 case 'G': case '8':
1356 term->mode |= YTERM_MODE_FORCED_UTF;
1357 break;
1358 case '@':
1359 term->mode &= ~YTERM_MODE_FORCED_UTF;
1360 break;
1361 default:
1362 fprintf(stderr, "esc unhandled charset: ESC %% %c\n",
1363 (cp > 0x20 && cp < 0x7f ? (char)cp : '.'));
1364 term->mode &= ~YTERM_MODE_FORCED_UTF;
1365 break;
1370 //==========================================================================
1372 // term_process_hash
1374 //==========================================================================
1375 static void term_process_hash (Term *term, uint32_t cp) {
1376 if (opt_dump_esc_enabled) {
1377 fprintf(stderr, "ESC-DUMP: ESC-# %c (%u)\n", (cp > 32 && cp < 127 ? (char)cp : '.'), cp);
1379 reset_esc(term);
1380 switch (cp) {
1381 case '8': /* DECALN -- DEC screen alignment test -- fill screen with E's */
1382 for (int y = 0; y < TERM_CBUF(term)->height; y += 1) {
1383 cbuf_write_wchar_count(TERM_CBUF(term), 0, y, 'E',
1384 TERM_CBUF(term)->width, TERM_CATTR(term));
1386 break;
1387 default:
1388 fprintf(stderr, "esc unhandled test: ESC # %c\n",
1389 (cp > 0x20 && cp < 0x7f ? (char)cp : '.'));
1390 term->mode &= ~YTERM_MODE_FORCED_UTF;
1391 break;
1396 //==========================================================================
1398 // term_process_osc
1400 //==========================================================================
1401 static void term_process_osc (Term *term, uint32_t cp) {
1402 yterm_assert(term->escseq.state == YTERM_ESC_OSC);
1403 // terminate on some control chars too (workaround for some buggy software)
1404 switch (cp) {
1405 case 0x07:
1406 finish_osc(term);
1407 reset_esc(term);
1408 return;
1409 case 0x08: // BS
1410 case 0x09: // BS
1411 case 0x0A: // LF
1412 case 0x0B: // VT
1413 case 0x0C: // FF
1414 case 0x0D: // CR
1415 reset_esc(term);
1416 term_putc(term, cp);
1417 return;
1418 case 0x0E:
1419 reset_esc(term);
1420 term->charset = YTERM_MODE_GFX1;
1421 return;
1422 case 0x0F:
1423 reset_esc(term);
1424 term->charset = YTERM_MODE_GFX0;
1425 return;
1426 case 0x18: // 0x18 aborts the sequence
1427 reset_esc(term);
1428 return;
1429 case 0x1A: // 0x1a aborts the sequence
1430 // 0x1a should print a reversed question mark (DEC does this), but meh
1431 reset_esc(term);
1432 term_putc(term, '?');
1433 return;
1434 case 0x1B: // ESC: finish current sequence, and restart a new one
1435 finish_osc(term);
1436 reset_esc(term);
1437 term->escseq.state = YTERM_ESC_ESC;
1438 return;
1439 case 0x7F: // ignore
1440 return;
1443 if (cp < 0x20) return; // ignore other control chars
1445 // normal char
1446 if (cp >= '0' && cp <= '9' && term->escseq.priv >= 0) {
1447 // command digit
1448 term->escseq.cmd = min2(255, term->escseq.cmd + 10 * ((int)cp - '0'));
1449 term->escseq.priv = min2(255, term->escseq.priv + 1);
1450 #if 0
1451 fprintf(stderr, "OSC-DIGIT: cmd=%d; priv=%d (%c)\n",
1452 term->escseq.cmd, term->escseq.priv, (char)cp);
1453 #endif
1454 } else if (cp == ';' && term->escseq.priv >= 0) {
1455 // we're done with the command
1456 term->escseq.priv = -1;
1457 switch (term->escseq.cmd) {
1458 case 0: case 2: term->escseq.cmd = 2; break;
1459 default: term->escseq.cmd = 0; break;
1461 } else {
1462 // collect command chars for title
1463 if (term->escseq.priv >= 0) {
1464 // bad command -- no ";" code terminator
1465 term->escseq.priv = -1;
1466 term->escseq.cmd = 0;
1468 if (term->escseq.cmd == 2) {
1469 // always use utf-8 for titles
1470 char utb[4];
1471 const int xlen = (int)yterm_utf8_encode(utb, cp);
1472 if (term->escseq.sbufpos + xlen < (int)sizeof(term->escseq.sbuf) - 1) {
1473 memcpy(term->escseq.sbuf + term->escseq.sbufpos, utb, (unsigned)xlen);
1474 term->escseq.sbufpos += xlen;
1475 term->escseq.sbuf[term->escseq.sbufpos] = 0;
1476 #if 0
1477 fprintf(stderr, "OSC-CHAR: cmd=%d; priv=%d (%c)\n",
1478 term->escseq.cmd, term->escseq.priv, (char)cp);
1479 #endif
1480 } else {
1481 term->escseq.sbufpos = (int)sizeof(term->escseq.sbuf);
1488 //==========================================================================
1490 // term_process_s_ign
1492 //==========================================================================
1493 static void term_process_s_ign (Term *term, uint32_t cp) {
1494 yterm_assert(term->escseq.state == YTERM_ESC_S_IGN);
1495 // terminate on some control chars too (workaround for some buggy software)
1496 switch (cp) {
1497 case 0x07:
1498 reset_esc(term);
1499 return;
1500 case 0x08: // BS
1501 case 0x09: // BS
1502 case 0x0A: // LF
1503 case 0x0B: // VT
1504 case 0x0C: // FF
1505 case 0x0D: // CR
1506 reset_esc(term);
1507 term_putc(term, cp);
1508 return;
1509 case 0x0E:
1510 reset_esc(term);
1511 term->charset = YTERM_MODE_GFX1;
1512 return;
1513 case 0x0F:
1514 reset_esc(term);
1515 term->charset = YTERM_MODE_GFX0;
1516 return;
1517 case 0x18: // 0x18 aborts the sequence
1518 reset_esc(term);
1519 return;
1520 case 0x1A: // 0x1a aborts the sequence
1521 // 0x1a should print a reversed question mark (DEC does this), but meh
1522 reset_esc(term);
1523 term_putc(term, '?');
1524 return;
1525 case 0x1B: // ESC: finish current sequence, and restart a new one
1526 reset_esc(term);
1527 term->escseq.state = YTERM_ESC_ESC;
1528 return;
1530 // eat all other chars
1534 //==========================================================================
1536 // term_process_query_string
1538 //==========================================================================
1539 static void term_process_query_string (Term *term) {
1540 if (term->escseq.sbufpos == 0 ||
1541 term->escseq.sbufpos >= (int)sizeof(term->escseq.sbuf))
1543 goto error;
1546 char buf[256];
1548 const char *str = term->escseq.sbuf;
1549 yterm_assert(str[term->escseq.sbufpos] == 0); // just in case
1551 #if 0
1552 fprintf(stderr, "Q:<%s>\n", str);
1553 #endif
1555 //TODO: proper parser (but i don't know what the fuck should be here)
1556 if (str[0] != '$') goto error;
1557 str += 1;
1558 if (str[0] == 'q' && str[1] == 'm' && str[2] == 0) {
1559 buf[0] = 0;
1560 snprintf(buf, sizeof(buf), "%s", "\x1bP1$r");
1561 char *p = buf + strlen(buf);
1563 if (term->wkscr->currattr.fg & CELL_ATTR_MASK) {
1564 const uint32_t fg = (term->wkscr->currattr.fg & ~CELL_ATTR_MASK);
1565 if (fg < 8) sprintf(p, "%u;", 30 + fg);
1566 else if (fg < 16) sprintf(p, "%u;", 90 + (fg - 8));
1567 else if (fg < 256) sprintf(p, "38;5;%u;", fg);
1568 } else {
1569 const uint32_t fg = term->wkscr->currattr.fg;
1570 sprintf(p, "38;2;%u;%u;%u", (fg >> 16) & 0xff, (fg >> 8) & 0xff, fg & 0xff);
1572 p += strlen(p);
1574 if (term->wkscr->currattr.bg & CELL_ATTR_MASK) {
1575 const uint32_t bg = (term->wkscr->currattr.bg & ~CELL_ATTR_MASK);
1576 if (bg < 8) sprintf(p, "%u;", 30 + bg);
1577 else if (bg < 16) sprintf(p, "%u;", 90 + (bg - 8));
1578 else if (bg < 256) sprintf(p, "38;5;%u;", bg);
1579 } else {
1580 const uint32_t bg = term->wkscr->currattr.bg;
1581 sprintf(p, "38;2;%u;%u;%u", (bg >> 16) & 0xff, (bg >> 8) & 0xff, bg & 0xff);
1583 p += strlen(p);
1585 if (p[-1] == ';') p -= 1;
1586 #if 0
1587 fprintf(stderr, "R:<%s>\n", buf + 1);
1588 #endif
1589 strcpy(p, "m\x1b[\\");
1591 return;
1594 error:
1595 // "not recognized" (i guess)
1596 term_write_raw(term, "\x1bP0$r\x1b[\\", -1);
1600 //==========================================================================
1602 // term_process_dcs
1604 //==========================================================================
1605 static void term_process_dcs (Term *term, uint32_t cp) {
1606 yterm_assert(term->escseq.state == YTERM_ESC_DCS);
1607 if (cp == 0x18 || cp == 0x1a || cp == 0x1b) {
1608 reset_esc(term);
1609 if (cp == 0x1b) {
1610 term_process_query_string(term);
1611 term->escseq.state = YTERM_ESC_ESC;
1613 } else {
1614 char utb[4];
1615 const int xlen = (int)yterm_utf8_encode(utb, cp);
1616 if (term->escseq.sbufpos + xlen < (int)sizeof(term->escseq.sbuf) - 1) {
1617 memcpy(term->escseq.sbuf + term->escseq.sbufpos, utb, (unsigned)xlen);
1618 term->escseq.sbufpos += xlen;
1619 term->escseq.sbuf[term->escseq.sbufpos] = 0;
1620 #if 0
1621 fprintf(stderr, "DCS-CHAR: cmd=%d; priv=%d (%c)\n",
1622 term->escseq.cmd, term->escseq.priv, (char)cp);
1623 #endif
1624 } else {
1625 term->escseq.sbufpos = (int)sizeof(term->escseq.sbuf);
1631 //==========================================================================
1633 // term_process_csi
1635 //==========================================================================
1636 static void term_process_csi (Term *term, uint32_t cp) {
1637 // CSI processing -- ESC [
1638 #if 0
1639 fprintf(stderr, "CSI: st=%d; cp='%c'; argc=%d; priv=%d; cmd=%d\n",
1640 term->escseq.state, (cp >= 0x20 && cp < 0x7f ? (char)cp : '*'),
1641 term->escseq.argc, term->escseq.priv, term->escseq.cmd);
1642 #endif
1643 if (csi_next_char(term, cp) != 0) {
1644 #if 0
1645 fprintf(stderr, "CSI-HANDLE: st=%d; cp='%c'; argc=%d; priv=%d; cmd=%d\n",
1646 term->escseq.state, (cp >= 0x20 && cp < 0x7f ? (char)cp : '*'),
1647 term->escseq.argc, term->escseq.priv, term->escseq.cmd);
1648 #endif
1649 csi_handle(term);
1654 //==========================================================================
1656 // term_process_control_char
1658 // returns non-zero if char was eaten
1660 //==========================================================================
1661 static yterm_bool term_process_control_char (Term *term, uint32_t cp) {
1662 yterm_bool res;
1663 // OSC will do it itself, for others it is the same
1664 // actually, OSC *may* want the same processing, but meh
1665 if (term->escseq.state != YTERM_ESC_OSC && term->escseq.state != YTERM_ESC_DCS) {
1666 res = 1;
1667 switch (cp) {
1668 case 7:
1669 // k8: hack: do not bell if we are in ESC sequence
1670 if (term->escseq.state == YTERM_ESC_NONE) term_putc(term, 0x07);
1671 break;
1672 case 14:
1673 term->charset = YTERM_MODE_GFX1;
1674 break;
1675 case 15:
1676 term->charset = YTERM_MODE_GFX0;
1677 break;
1678 case 0x1a: // 0x18 and 0x1a aborts the sequence
1679 // 0x1a should print a reversed question mark (DEC does this), but meh
1680 term_putc(term, '?');
1681 /* fallthrough */
1682 case 0x18: // 0x18 and 0x1a aborts the sequence
1683 reset_esc(term);
1684 break;
1685 case 0x1b: // ESC: finish current sequence, and restart a new one
1686 finish_osc(term);
1687 reset_esc(term);
1688 term->escseq.state = YTERM_ESC_ESC;
1689 break;
1690 case 127:
1691 break; // ignore it
1692 default:
1693 if (cp < 0x20) {
1694 term_putc(term, cp);
1695 } else {
1696 res = 0;
1698 break;
1700 } else {
1701 res = 0;
1703 return res;
1707 //==========================================================================
1709 // term_process_esc
1711 // process first char after ESC (sequence start)
1713 //==========================================================================
1714 static void term_process_esc (Term *term, uint32_t cp) {
1715 if (opt_dump_esc_enabled) {
1716 fprintf(stderr, "ESC-DUMP: ESC %c (%u)\n", (cp > 32 && cp < 127 ? (char)cp : '.'), cp);
1718 reset_esc(term);
1719 switch (cp) {
1720 case '[': start_csi(term); break;
1721 case ']': start_osc(term); break;
1722 case '(': term->escseq.state = YTERM_ESC_ALT_CHARSET_G0; break;
1723 case ')': term->escseq.state = YTERM_ESC_ALT_CHARSET_G1; break;
1724 case '#': term->escseq.state = YTERM_ESC_HASH; break;
1725 case '%': term->escseq.state = YTERM_ESC_PERCENT; break;
1726 case '\\': // noop sequence; used to finish OSCs
1727 break;
1728 case 'D': /* IND -- Linefeed */
1729 term_lcf_reset(term);
1730 // some sources says that it works only when cursor is in scroll area
1731 if (TERM_CPOS(term)->y >= TERM_SCTOP(term) &&
1732 TERM_CPOS(term)->y <= TERM_SCBOT(term))
1734 if (TERM_CPOS(term)->y == TERM_SCBOT(term)) {
1735 term_scroll_up(term, 1);
1736 } else {
1737 term_curmove_rel(term, 0, 1);
1740 break;
1741 case 'E': /* NEL -- Next line */
1742 term_lcf_reset(term);
1743 term_putc(term, '\n');
1744 term_putc(term, '\r'); // always go to the first column
1745 break;
1746 case 'F': /* xterm extension: Cursor to lower left corner of screen */
1747 term_curmove_abs(term, 0, TERM_CBUF(term)->height - 1);
1748 break;
1749 case 'H': /* HTS -- set custom tab position */
1750 fprintf(stderr, "WARNING: ESC H (custom tab position) is not supported.\n");
1751 break;
1752 case 'M': /* RI -- Reverse linefeed */
1753 term_lcf_reset(term);
1754 // some sources says that it works only when cursor is in scroll area
1755 if (TERM_CPOS(term)->y >= TERM_SCTOP(term) &&
1756 TERM_CPOS(term)->y <= TERM_SCBOT(term))
1758 if (TERM_CPOS(term)->y == TERM_SCTOP(term)) {
1759 term_scroll_down(term, 1);
1760 } else {
1761 term_curmove_rel(term, 0, -1);
1764 break;
1765 case 'P': /* DCS -- Device control string */
1766 reset_esc(term);
1767 start_dcs(term);
1768 break;
1769 case '^': /* PM -- Privacy message */
1770 reset_esc(term);
1771 // use DCS mode here, because both this and DCS are completely ignored
1772 term->escseq.state = YTERM_ESC_S_IGN;
1773 break;
1774 case 'c': /* RIS -- Reset to inital state */
1775 term_reset_mode(term);
1776 break;
1777 #if 0
1778 case 'l': /* xterm extension: lock memory above the cursor */
1779 case 'm': /* xterm extension: unlock memory */
1780 #endif
1781 case '=': /* DECPAM -- Application keypad */
1782 term->mode |= YTERM_MODE_APPKEYPAD;
1783 break;
1784 case '>': /* DECPNM -- Normal keypad */
1785 term->mode &= ~YTERM_MODE_APPKEYPAD;
1786 break;
1787 case '7': /* DECSC -- Save Cursor */
1788 /* Save current state (cursor coordinates, attributes, character sets pointed at by G0, G1) */
1789 term_save_cursor(term, SaveDEC);
1790 break;
1791 case '8': /* DECRC -- Restore Cursor */
1792 term_restore_cursor(term, SaveDEC);
1793 break;
1794 case 'Z': /* DEC private identification */
1795 term_write_raw(term, "\x1b[?1;2c", -1);
1796 break;
1797 default:
1798 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X ('%c')\n",
1799 cp, (cp > 0x20 && cp < 0x7f ? (char)cp : '.'));
1800 break;
1802 #if 0
1803 fprintf(stderr, "ESC-START: st=%d; cp=0%04XH\n", term->escseq.state, cp);
1804 #endif
1808 //==========================================================================
1810 // term_process_char
1812 // process escape sequences and such
1814 //==========================================================================
1815 static void term_process_char (Term *term, uint32_t cp) {
1816 if (term == NULL) return;
1818 // debug dump
1819 if (opt_dump_fd >= 0 && opt_dump_fd_enabled) {
1820 char utshit[4];
1821 write(opt_dump_fd, utshit, yterm_utf8_encode(utshit, cp));
1824 // process control chars
1825 if (!term_process_control_char(term, cp)) {
1826 // now parse cp according to the state
1827 switch (term->escseq.state) {
1828 case YTERM_ESC_NONE: term_putc(term, cp); break;
1829 case YTERM_ESC_ESC: term_process_esc(term, cp); break;
1830 case YTERM_ESC_ALT_CHARSET_G0: term_process_altg0(term, cp); break;
1831 case YTERM_ESC_ALT_CHARSET_G1: term_process_altg1(term, cp); break;
1832 case YTERM_ESC_HASH: term_process_hash(term, cp); break;
1833 case YTERM_ESC_PERCENT: term_process_percent(term, cp); break;
1834 case YTERM_ESC_CSI: term_process_csi(term, cp); break;
1835 case YTERM_ESC_CSI_IGNORE: term_process_csi(term, cp); break;
1836 case YTERM_ESC_OSC: term_process_osc(term, cp); break;
1837 case YTERM_ESC_DCS: term_process_dcs(term, cp); break;
1838 case YTERM_ESC_S_IGN: term_process_s_ign(term, cp); break;
1839 default: yterm_assert("ketmar forgot to process some ESC sequence parser state");
1843 #ifdef USELESS_DEBUG_FEATURES
1844 if (opt_debug_slowdown) {
1845 if (term->scroll_count != 0 || TERM_CBUF(term)->dirtyCount != 0) {
1846 term->scroll_count = 0;
1847 cbuf_mark_all_dirty(TERM_CBUF(term));
1848 CALL_RENDER(
1849 renderDirty(rcbuf, rcpos);
1851 // debug slowdown
1852 XSync(x11_dpy, False);
1853 struct timespec ts;
1854 ts.tv_sec = 0;
1855 ts.tv_nsec = 50 * 1000000;
1856 nanosleep(&ts, NULL);
1857 XSync(x11_dpy, False);
1860 #endif