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_) \
26 ? (TERM_IN_SELECTION(tt_) ? hvbuf.dirtyCount : TERM_CBUF(tt_)->dirtyCount) \
29 #define CALL_RENDER(code_) do { \
30 const yterm_bool oldgi = cbufInverse; \
31 const int oldcm = colorMode; \
35 Region clipreg = x11_render_osd_menus(curr_menu); \
36 if (term->osd_messages != NULL) { \
38 colorMode = CMODE_NORMAL; \
39 clipreg = x11_render_osd(term->osd_messages->message, clipreg); \
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; \
48 colorMode = (opt_amber_tint ? CMODE_AMBER_TINT : CMODE_AMBER); \
51 scpos.x = term->history.cx; \
52 scpos.y = term->history.cy; \
54 XSetRegion(x11_dpy, x11_gc, clipreg); \
55 XDestroyRegion(clipreg); /* we don't need it anymore */ \
57 /*if (term->osd_messages != NULL || curr_menu != NULL)*/ { \
58 /* reset OSD clip mask */ \
59 XSetClipMask(x11_dpy, x11_gc, None); \
61 cbufInverse = oldgi; \
66 //==========================================================================
68 // term_send_size_ioctl
70 //==========================================================================
71 static void term_send_size_ioctl (Term
*term
) {
72 if (term
!= NULL
&& term
->child
.cmdfd
>= 0) {
74 w
.ws_row
= TERM_CBUF(term
)->height
;
75 w
.ws_col
= TERM_CBUF(term
)->width
;
78 if (ioctl(term
->child
.cmdfd
, TIOCSWINSZ
, &w
) < 0) {
79 fprintf(stderr
, "WARNING: couldn't set window size: %s\n", strerror(errno
));
85 //==========================================================================
89 //==========================================================================
90 static void term_reset_mode (Term
*term
) {
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 //==========================================================================
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
;
151 term
->colorMode
= -1;
152 term
->mouse_reports
= -1;
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;
171 term
->last_acttime
= 0;
173 term_reset_mode(term
);
180 //==========================================================================
182 // term_wipe_osd_messages
184 //==========================================================================
185 static void term_wipe_osd_messages (Term
*term
) {
187 while (term
->osd_messages
!= NULL
) {
188 Message
*msg
= term
->osd_messages
;
189 term
->osd_messages
= msg
->next
;
196 //==========================================================================
200 // this WIPES the whole struct
202 //==========================================================================
203 static void term_cleanup (Term
*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 //==========================================================================
221 //==========================================================================
222 static void term_close_fd (Term
*term
) {
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 //==========================================================================
238 // kill child, close fd, free output buffer
240 //==========================================================================
241 static void term_kill_child (Term
*term
) {
243 if (term
->child
.pid
!= 0) {
244 kill(term
->child
.pid
, SIGKILL
);
252 //==========================================================================
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; }
275 Message
*last
= term
->osd_messages
;
277 msg
->time
= yterm_get_msecs() + (unsigned)timeout
;
278 term
->osd_messages
= msg
;
280 msg
->time
= (unsigned)timeout
;
281 while (last
->next
!= NULL
) last
= last
->next
;
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 // ////////////////////////////////////////////////////////////////////////// //
305 SaveANSI
= 2, // same as DEC for now
310 //==========================================================================
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 //==========================================================================
358 //==========================================================================
359 YTERM_STATIC_INLINE
int sign (int v
) {
360 return (v
< 0 ? -1 : v
> 0 ? +1 : 0);
364 //==========================================================================
366 // term_scroll_internal
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
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
);
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?
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
);
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
);
428 cbuf_scroll_area_up(cbuf
, stop
, sbot
, lines
,
429 (do_copy
? CBUF_DIRTY_OVERWRITE
: CBUF_DIRTY_MERGE
),
433 cbuf_scroll_area_down(cbuf
, stop
, sbot
, lines
,
434 (do_copy
? CBUF_DIRTY_OVERWRITE
: CBUF_DIRTY_MERGE
),
438 // put the cursor back (because why not)
439 term_invalidate_cursor(term
);
444 //==========================================================================
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) {
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 //==========================================================================
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 //==========================================================================
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;
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
);
493 if (cpos
->y
== TERM_SCBOT(term
)) {
494 term_scroll_up(term
, 1);
495 } else if (cpos
->y
!= TERM_CBUF(term
)->height
- 1) {
497 term_invalidate_cursor(term
);
503 //==========================================================================
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 //==========================================================================
528 //==========================================================================
529 static void term_curmove_abs (Term
*term
, int nx
, int ny
) {
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
));
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
;
544 term_invalidate_cursor(term
);
550 //==========================================================================
554 //==========================================================================
555 static void term_curmove_rel (Term
*term
, int dx
, int dy
) {
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);
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
));
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
;
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 //==========================================================================
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
);
605 if ((term
->mode
& term
->charset
) != 0 && cp
>= 0x60 && cp
< 0x7f) {
607 if (opt_terminus_gfx
!= 0) {
610 cp
= term_font_gfx_table
[cp
- 0x60];
613 if (cp
== 0x7f) return; //k8: dunno
614 if (cpos
->justMoved
) {
616 cbuf_unmark_line_autowrap(cbuf
, cpos
->y
);
619 //const yterm_bool oldlcf = TERM_CPOS(term)->lastColFlag;
620 //if (oldlcf) term_erase_cursor(term);
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
));
628 if ((term
->mode
& YTERM_MODE_WRAP
) != 0) {
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
);
635 term_invalidate_cursor(term
);
637 term_invalidate_cursor(term
);
641 if (cpos
->x
!= cbuf
->width
- 1) {
642 term_invalidate_cursor(term
);
644 term_invalidate_cursor(term
);
646 cbuf_unmark_line_autowrap(cbuf
, cpos
->y
);
649 //if (oldlcf) term_draw_cursor(term);
656 // do not spam with bells
657 uint64_t ctt
= yterm_get_msecs();
658 if (ctt
- lastBellTime
>= 100) {
661 if (!winFocused
|| !winVisible
) {
669 if (cpos->lastColFlag != 0) {
670 cpos->lastColFlag = 0;
671 term_lcf_reset(term); // indicate cursor movement
675 term_lcf_reset(term
);
677 if (cpos
->x
!= 0 || cpos
->y
!= 0) {
678 term_invalidate_cursor(term
);
680 cpos
->x
= cbuf
->width
- 1;
685 term_invalidate_cursor(term
);
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);
695 term_invalidate_cursor(term
);
697 term_invalidate_cursor(term
);
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) {
710 term_invalidate_cursor(term
);
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
);
720 term_lcf_reset(term
);
721 cbuf_unmark_line_autowrap(cbuf
, cpos
->y
);
723 term_invalidate_cursor(term
);
725 term_invalidate_cursor(term
);
728 case 0x1A: // SUB: according to "wraptest", this should clear the flag too
729 term_lcf_reset_nocmove(term
);
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;
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
));
802 //==========================================================================
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
);
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
);
818 if (neww
!= TERM_CBUF(term
)->width
|| newh
!= TERM_CBUF(term
)->height
) {
820 fprintf(stderr
, "NEW TERM SIZE: %dx%d\n", neww
, newh
);
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 */
836 term
->main
.scTop
= 0; term
->main
.scBot
= newh
- 1;
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 */
846 cbuf_free(&term
->alt
.cbuf
);
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
);
860 fprintf(stderr
, "NEW REAL TERM SIZE: %dx%d\n",
861 term
->wkscr
->cbuf
.width
, term
->wkscr
->cbuf
.height
);
871 //==========================================================================
875 //==========================================================================
876 static yterm_bool
term_can_read (Term
*term
) {
877 if (term
!= NULL
&& term
->deadstate
== DS_ALIVE
&& term
->child
.cmdfd
>= 0) {
880 struct timeval timeout
= {0};
882 FD_SET(term
->child
.cmdfd
, &rfd
);
883 const int sres
= select(term
->child
.cmdfd
+ 1, &rfd
, NULL
, NULL
, &timeout
);
885 if (errno
== EINTR
) continue;
886 //k8t_die("select failed: %s", strerror(errno));
889 return (sres
> 0 /*&& FD_ISSET(term->cmdfd, &rfd)*/ ? 1 : 0);
898 //==========================================================================
902 //==========================================================================
903 static yterm_bool
term_can_write (Term
*term
) {
904 if (term
!= NULL
&& term
->deadstate
== DS_ALIVE
&& term
->child
.cmdfd
>= 0) {
907 struct timeval timeout
= {0};
909 FD_SET(term
->child
.cmdfd
, &wfd
);
910 const int sres
= select(term
->child
.cmdfd
+ 1, NULL
, &wfd
, NULL
, &timeout
);
912 if (errno
== EINTR
) continue;
915 return (sres
> 0 /*&& FD_ISSET(term->child.cmdfd, &wfd)*/ ? 1 : 0);
924 //==========================================================================
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
) {
936 if (term
->wrbuf
.size
>= 0x00200000U
||
937 0x00200000U
- term
->wrbuf
.size
< (uint32_t)buflen
)
939 fprintf(stderr
, "ERROR: terminal output buffer overflow!\n");
944 if (term
->wrbuf
.used
+ (uint32_t)buflen
<= WRITE_BUF_SIZE
) {
945 newsz
= WRITE_BUF_SIZE
;
947 newsz
= ((term
->wrbuf
.used
+ (uint32_t)buflen
) | 4095) + 1;
949 char *nbuf
= realloc(term
->wrbuf
.buf
, newsz
);
951 fprintf(stderr
, "ERROR: out of memory for terminal output buffer!\n");
955 #ifdef DUMP_WRITE_GROW
956 fprintf(stderr
, "GROWING: %u -> %u (used: %u)\n",
957 term
->wrbuf
.size
, newsz
, term
->wrbuf
.used
);
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
) {
971 //==========================================================================
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) {
980 const char *end
= str
;
981 while (*end
> 0 && *end
< 127) end
+= 1;
982 // write what we have
984 term_write_raw(term
, str
, (int)(ptrdiff_t)(end
- str
));
992 cp
= yterm_utf8d_consume(cp
, *str
); str
+= 1;
993 if (yterm_utf8_valid_cp(cp
)) {
994 char koi
= yterm_uni2koi(cp
);
996 if (dcpos
== (int)sizeof(dcbuf
)) {
997 term_write_raw(term
, dcbuf
, dcpos
);
1005 if (dcpos
!= 0) term_write_raw(term
, dcbuf
, dcpos
);
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)
1026 while (slen
> 0 && term
->wrbuf
.used
< 1024*1024*32) { // arbitrary limit
1027 // try to flush the buffer
1028 if (term
->wrbuf
.used
> 65535) {
1030 fprintf(stderr
, "...trying to send %u bytes...\n", term
->wrbuf
.used
);
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
;
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);
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) {
1061 term_write_raw(term
, "\x1b[I", -1);
1063 term_write_raw(term
, "\x1b[O", -1);
1069 //==========================================================================
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
)
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
1107 button
-= YTERM_MR_LEFT
;
1108 if (button
>= 3) button
+= 64 - 3;
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
;
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 */
1127 sprintf(p
, "%d;", button
+ss
);
1130 case 1015: /* urxvt */
1131 p
-= 1; // remove 'M'
1132 sprintf(p
, "%d;", 32 + button
+ ss
);
1136 *p
= 32 + button
+ ss
;
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);
1147 case 1006: /* sgr */
1148 sprintf(p
, "%d;%d%c", x
+ 1, y
+ 1, lastCh
);
1151 case 1015: /* urxvt */
1152 sprintf(p
, "%d;%dM", x
+ 1, y
+ 1);
1156 sprintf(p
, "%c%c", 32 + x
+ 1, 32 + y
+ 1);
1162 term_write_raw(term
, buf
, -1);
1167 // ////////////////////////////////////////////////////////////////////////// //
1170 YTERM_ESC_ESC
, // seen ESC
1173 YTERM_ESC_CSI_IGNORE
,
1175 YTERM_ESC_ALT_CHARSET_G0
,
1176 YTERM_ESC_ALT_CHARSET_G1
,
1183 YTERM_ESC_S_IGN
, // string, ignore
1187 //==========================================================================
1191 // should touch only state!
1193 //==========================================================================
1194 YTERM_STATIC_INLINE
void reset_esc (Term
*term
) {
1195 term
->escseq
.state
= YTERM_ESC_NONE
;
1199 //==========================================================================
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 //==========================================================================
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 //==========================================================================
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 //==========================================================================
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
;
1258 // remove leading spaces
1260 while (ntt
[nlen
] && ((unsigned char *)ntt
)[nlen
] == 32) nlen
+= 1;
1261 if (nlen
!= 0) memmove(ntt
, ntt
+ nlen
, strlen(ntt
+ nlen
) + 1);
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
);
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
);
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
);
1299 case '0': /* Line drawing crap */
1300 term
->mode
|= YTERM_MODE_GFX0
;
1302 case 'B': /* Back to regular text */
1303 case 'U': /* character ROM */
1304 case 'K': /* user mapping */
1305 term
->mode
&= ~YTERM_MODE_GFX0
;
1308 fprintf(stderr
, "esc unhandled charset: ESC ( %c\n",
1309 (cp
> 0x20 && cp
< 0x7f ? (char)cp
: '.'));
1310 term
->mode
&= ~YTERM_MODE_GFX0
;
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
);
1327 case '0': /* Line drawing crap */
1328 term
->mode
|= YTERM_MODE_GFX1
;
1330 case 'B': /* Back to regular text */
1331 case 'U': /* character ROM */
1332 case 'K': /* user mapping */
1333 term
->mode
&= ~YTERM_MODE_GFX1
;
1336 fprintf(stderr
, "esc unhandled charset: ESC ) %c\n",
1337 (cp
> 0x20 && cp
< 0x7f ? (char)cp
: '.'));
1338 term
->mode
&= ~YTERM_MODE_GFX1
;
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
);
1356 term
->mode
|= YTERM_MODE_FORCED_UTF
;
1359 term
->mode
&= ~YTERM_MODE_FORCED_UTF
;
1362 fprintf(stderr
, "esc unhandled charset: ESC %% %c\n",
1363 (cp
> 0x20 && cp
< 0x7f ? (char)cp
: '.'));
1364 term
->mode
&= ~YTERM_MODE_FORCED_UTF
;
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
);
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
));
1388 fprintf(stderr
, "esc unhandled test: ESC # %c\n",
1389 (cp
> 0x20 && cp
< 0x7f ? (char)cp
: '.'));
1390 term
->mode
&= ~YTERM_MODE_FORCED_UTF
;
1396 //==========================================================================
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)
1416 term_putc(term
, cp
);
1420 term
->charset
= YTERM_MODE_GFX1
;
1424 term
->charset
= YTERM_MODE_GFX0
;
1426 case 0x18: // 0x18 aborts the sequence
1429 case 0x1A: // 0x1a aborts the sequence
1430 // 0x1a should print a reversed question mark (DEC does this), but meh
1432 term_putc(term
, '?');
1434 case 0x1B: // ESC: finish current sequence, and restart a new one
1437 term
->escseq
.state
= YTERM_ESC_ESC
;
1439 case 0x7F: // ignore
1443 if (cp
< 0x20) return; // ignore other control chars
1446 if (cp
>= '0' && cp
<= '9' && term
->escseq
.priv
>= 0) {
1448 term
->escseq
.cmd
= min2(255, term
->escseq
.cmd
+ 10 * ((int)cp
- '0'));
1449 term
->escseq
.priv
= min2(255, term
->escseq
.priv
+ 1);
1451 fprintf(stderr
, "OSC-DIGIT: cmd=%d; priv=%d (%c)\n",
1452 term
->escseq
.cmd
, term
->escseq
.priv
, (char)cp
);
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;
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
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;
1477 fprintf(stderr
, "OSC-CHAR: cmd=%d; priv=%d (%c)\n",
1478 term
->escseq
.cmd
, term
->escseq
.priv
, (char)cp
);
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)
1507 term_putc(term
, cp
);
1511 term
->charset
= YTERM_MODE_GFX1
;
1515 term
->charset
= YTERM_MODE_GFX0
;
1517 case 0x18: // 0x18 aborts the sequence
1520 case 0x1A: // 0x1a aborts the sequence
1521 // 0x1a should print a reversed question mark (DEC does this), but meh
1523 term_putc(term
, '?');
1525 case 0x1B: // ESC: finish current sequence, and restart a new one
1527 term
->escseq
.state
= YTERM_ESC_ESC
;
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
))
1548 const char *str
= term
->escseq
.sbuf
;
1549 yterm_assert(str
[term
->escseq
.sbufpos
] == 0); // just in case
1552 fprintf(stderr
, "Q:<%s>\n", str
);
1555 //TODO: proper parser (but i don't know what the fuck should be here)
1556 if (str
[0] != '$') goto error
;
1558 if (str
[0] == 'q' && str
[1] == 'm' && str
[2] == 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
);
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);
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
);
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);
1585 if (p
[-1] == ';') p
-= 1;
1587 fprintf(stderr
, "R:<%s>\n", buf
+ 1);
1589 strcpy(p
, "m\x1b[\\");
1595 // "not recognized" (i guess)
1596 term_write_raw(term
, "\x1bP0$r\x1b[\\", -1);
1600 //==========================================================================
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) {
1610 term_process_query_string(term
);
1611 term
->escseq
.state
= YTERM_ESC_ESC
;
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;
1621 fprintf(stderr
, "DCS-CHAR: cmd=%d; priv=%d (%c)\n",
1622 term
->escseq
.cmd
, term
->escseq
.priv
, (char)cp
);
1625 term
->escseq
.sbufpos
= (int)sizeof(term
->escseq
.sbuf
);
1631 //==========================================================================
1635 //==========================================================================
1636 static void term_process_csi (Term
*term
, uint32_t cp
) {
1637 // CSI processing -- ESC [
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
);
1643 if (csi_next_char(term
, cp
) != 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
);
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
) {
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
) {
1669 // k8: hack: do not bell if we are in ESC sequence
1670 if (term
->escseq
.state
== YTERM_ESC_NONE
) term_putc(term
, 0x07);
1673 term
->charset
= YTERM_MODE_GFX1
;
1676 term
->charset
= YTERM_MODE_GFX0
;
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
, '?');
1682 case 0x18: // 0x18 and 0x1a aborts the sequence
1685 case 0x1b: // ESC: finish current sequence, and restart a new one
1688 term
->escseq
.state
= YTERM_ESC_ESC
;
1694 term_putc(term
, cp
);
1707 //==========================================================================
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
);
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
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);
1737 term_curmove_rel(term
, 0, 1);
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
1746 case 'F': /* xterm extension: Cursor to lower left corner of screen */
1747 term_curmove_abs(term
, 0, TERM_CBUF(term
)->height
- 1);
1749 case 'H': /* HTS -- set custom tab position */
1750 fprintf(stderr
, "WARNING: ESC H (custom tab position) is not supported.\n");
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);
1761 term_curmove_rel(term
, 0, -1);
1765 case 'P': /* DCS -- Device control string */
1769 case '^': /* PM -- Privacy message */
1771 // use DCS mode here, because both this and DCS are completely ignored
1772 term
->escseq
.state
= YTERM_ESC_S_IGN
;
1774 case 'c': /* RIS -- Reset to inital state */
1775 term_reset_mode(term
);
1778 case 'l': /* xterm extension: lock memory above the cursor */
1779 case 'm': /* xterm extension: unlock memory */
1781 case '=': /* DECPAM -- Application keypad */
1782 term
->mode
|= YTERM_MODE_APPKEYPAD
;
1784 case '>': /* DECPNM -- Normal keypad */
1785 term
->mode
&= ~YTERM_MODE_APPKEYPAD
;
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
);
1791 case '8': /* DECRC -- Restore Cursor */
1792 term_restore_cursor(term
, SaveDEC
);
1794 case 'Z': /* DEC private identification */
1795 term_write_raw(term
, "\x1b[?1;2c", -1);
1798 fprintf(stderr
, "erresc: unknown sequence ESC 0x%02X ('%c')\n",
1799 cp
, (cp
> 0x20 && cp
< 0x7f ? (char)cp
: '.'));
1803 fprintf(stderr
, "ESC-START: st=%d; cp=0%04XH\n", term
->escseq
.state
, cp
);
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;
1819 if (opt_dump_fd
>= 0 && opt_dump_fd_enabled
) {
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
));
1849 renderDirty(rcbuf
, rcpos
);
1852 XSync(x11_dpy
, False
);
1855 ts
.tv_nsec
= 50 * 1000000;
1856 nanosleep(&ts
, NULL
);
1857 XSync(x11_dpy
, False
);