1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: termout.unx.c 955 2008-03-06 23:52:36Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2021 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
21 #include "../../c-client/mail.h" /* for MAILSTREAM and friends */
22 #include "../../c-client/osdep.h"
23 #include "../../c-client/rfc822.h" /* for soutr_t and such */
24 #include "../../c-client/misc.h" /* for cpystr proto */
25 #include "../../c-client/utf8.h" /* for CHARSET and such*/
26 #include "../../c-client/imap4r1.h"
28 #include "../../pith/osdep/color.h"
30 #include "../../pith/debug.h"
31 #include "../../pith/conf.h"
32 #include "../../pith/newmail.h"
33 #include "../../pith/charconv/utf8.h"
35 #include "../../pico/estruct.h"
36 #include "../../pico/pico.h"
37 #include "../../pico/edef.h"
38 #include "../../pico/efunc.h"
39 #include "../../pico/osdep/color.h"
41 #include "../status.h"
42 #include "../keymenu.h"
43 #include "../titlebar.h"
45 #include "termout.gen.h"
46 #include "termout.unx.h"
49 /*======================================================================
50 Routines for painting the screen
51 - figure out what the terminal type is
52 - deal with screen size changes
53 - save special output sequences
54 - the usual screen clearing, cursor addressing and scrolling
57 This library gives programs the ability to easily access the
58 termcap information and write screen oriented and raw input
59 programs. The routines can be called as needed, except that
60 to use the cursor / screen routines there must be a call to
61 InitScreen() first. The 'Raw' input routine can be used
62 independently, however. (Elm comment)
64 Not sure what the original source of this code was. It got to be
65 here as part of ELM. It has been changed significantly from the
66 ELM version to be more robust in the face of inconsistent terminal
67 autowrap behaviour. Also, the unused functions were removed, it was
68 made to pay attention to the window size, and some code was made nicer
69 (in my opinion anyways). It also outputs the terminal initialization
70 strings and provides for minimal scrolling and detects terminals
71 with out enough capabilities. (Pine comment, 1990)
74 This code used to pay attention to the "am" auto margin and "xn"
75 new line glitch fields, but they were so often incorrect because many
76 terminals can be configured to do either that we've taken it out. It
77 now assumes it doesn't know where the cursor is after outputting in the
82 static int _lines
, _columns
;
88 static int outchar(int);
89 static void moveabsolute(int, int);
90 static void CursorUp(int);
91 static void CursorDown(int);
92 static void CursorLeft(int);
93 static void CursorRight(int);
96 extern char *_clearscreen
, *_moveto
, *_up
, *_down
, *_right
, *_left
,
97 *_setinverse
, *_clearinverse
,
98 *_cleartoeoln
, *_cleartoeos
,
99 *_startinsert
, *_endinsert
, *_insertchar
, *_deletechar
,
100 *_deleteline
, *_insertline
,
101 *_scrollregion
, *_scrollup
, *_scrolldown
,
102 *_termcap_init
, *_termcap_end
;
103 extern char term_name
[];
104 extern int _tlines
, _tcolumns
, _bce
;
106 static enum {NoScroll
,UseScrollRegion
,InsertDelete
} _scrollmode
;
108 extern int tputs(char *, int, int (*)(int));
109 extern char *tgoto(char *, int, int);
113 /*----------------------------------------------------------------------
114 Initialize the screen for output, set terminal type, etc
116 Args: tt -- Pointer to variable to store the tty output structure.
118 Result: terminal size is discovered and set in pine state
119 termcap entry is fetched and stored
120 make sure terminal has adequate capabilities
121 evaluate scrolling situation
122 returns status of indicating the state of the screen/termcap entry
125 -1 indicating no terminal name associated with this shell,
126 -2..-n No termcap for this terminal type known
127 -3 Can't open termcap file
128 -4 Terminal not powerful enough - missing clear to eoln or screen
132 config_screen(struct ttyo
**tt
)
137 ttyo
= (struct ttyo
*)fs_get(sizeof (struct ttyo
));
139 _line
= 0; /* where are we right now?? */
140 _col
= 0; /* assume zero, zero... */
143 * This is an ugly hack to let vtterminalinfo know it's being called
147 if((err
= vtterminalinfo(F_ON(F_TCAP_WINS
, ps_global
))) != 0)
153 _lines
= DEFAULT_LINES_ON_TERMINAL
;
158 _columns
= DEFAULT_COLUMNS_ON_TERMINAL
;
160 _columns
= _tcolumns
;
164 ttyo
->header_rows
= 2;
165 ttyo
->footer_rows
= 3;
167 /*---- Make sure this terminal has the capability.
168 All we need is cursor address, clear line, and
171 if(_moveto
== NULL
|| _cleartoeoln
== NULL
||
172 _setinverse
== NULL
|| _clearinverse
== NULL
) {
176 dprint((1, "Terminal type: %s\n", term_name
? term_name
: "?"));
178 /*------ Figure out scrolling mode -----*/
179 if(_scrollregion
!= NULL
&& _scrollregion
[0] != '\0' &&
180 _scrollup
!= NULL
&& _scrollup
[0] != '\0'){
181 _scrollmode
= UseScrollRegion
;
182 } else if(_insertline
!= NULL
&& _insertline
[0] != '\0' &&
183 _deleteline
!= NULL
&& _deleteline
[0] != '\0') {
184 _scrollmode
= InsertDelete
;
186 _scrollmode
= NoScroll
;
188 dprint((7, "Scroll mode: %s\n",
189 _scrollmode
==NoScroll
? "No Scroll" :
190 _scrollmode
==InsertDelete
? "InsertDelete" : "Scroll Regions"));
203 /*----------------------------------------------------------------------
204 Initialize the screen with the termcap string
209 if(_termcap_init
) /* init using termcap's rule */
210 tputs(_termcap_init
, 1, outchar
);
212 /* and make sure there are no scrolling surprises! */
213 BeginScroll(0, ps_global
->ttyo
->screen_rows
- 1);
215 pico_toggle_color(0);
216 switch(ps_global
->color_style
){
219 pico_set_color_options(pico_trans_color() ? COLOR_TRANS_OPT
: 0);
222 pico_set_color_options(COLOR_ANSI8_OPT
|COLOR_TRANS_OPT
);
225 pico_set_color_options(COLOR_ANSI16_OPT
|COLOR_TRANS_OPT
);
228 pico_set_color_options(COLOR_ANSI256_OPT
|COLOR_TRANS_OPT
);
232 if(ps_global
->color_style
!= COL_NONE
)
233 pico_toggle_color(1);
236 if(pico_usingcolor()){
237 if(ps_global
->VAR_NORM_FORE_COLOR
)
238 pico_nfcolor(ps_global
->VAR_NORM_FORE_COLOR
);
240 if(ps_global
->VAR_NORM_BACK_COLOR
)
241 pico_nbcolor(ps_global
->VAR_NORM_BACK_COLOR
);
243 if(ps_global
->VAR_REV_FORE_COLOR
)
244 pico_rfcolor(ps_global
->VAR_REV_FORE_COLOR
);
246 if(ps_global
->VAR_REV_BACK_COLOR
)
247 pico_rbcolor(ps_global
->VAR_REV_BACK_COLOR
);
249 pico_set_normal_color();
252 /* and make sure icon text starts out consistent */
253 icon_text(NULL
, IT_NEWMAIL
);
260 /*----------------------------------------------------------------------
261 Get the current window size
263 Args: ttyo -- pointer to structure to store window size in
265 NOTE: we don't override the given values unless we know better
268 get_windsize(struct ttyo
*ttyo
)
270 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
274 * Get the window size from the tty driver. If we can't fish it from
275 * stdout (pine's output is directed someplace else), try stdin (which
276 * *must* be associated with the terminal; see init_tty_driver)...
278 if(ioctl(1, TIOCGWINSZ
, &win
) >= 0 /* 1 is stdout */
279 || ioctl(0, TIOCGWINSZ
, &win
) >= 0){ /* 0 is stdin */
281 _lines
= MIN(win
.ws_row
, MAX_SCREEN_ROWS
);
284 _columns
= MIN(win
.ws_col
, MAX_SCREEN_COLS
);
286 dprint((2, "new win size -----<%d %d>------\n",
290 /* Depending on the OS, the ioctl() may have failed because
291 of a 0 rows, 0 columns setting. That happens on DYNIX/ptx 1.3
292 (with a kernel patch that happens to involve the negotiation
293 of window size in the telnet streams module.) In this case
294 the error is EINVARG. Leave the default settings. */
295 dprint((1, "ioctl(TIOCWINSZ) failed :%s\n",
296 error_description(errno
)));
300 ttyo
->screen_cols
= MIN(_columns
, MAX_SCREEN_COLS
);
301 ttyo
->screen_rows
= MIN(_lines
, MAX_SCREEN_ROWS
);
306 /*----------------------------------------------------------------------
307 End use of the screen.
308 Print status message, if any.
309 Flush status messages.
312 end_screen(char *message
, int exit_val
)
314 int footer_rows_was_one
= 0;
318 dprint((9, "end_screen called\n"));
320 if(FOOTER_ROWS(ps_global
) == 1){
321 footer_rows_was_one
++;
322 FOOTER_ROWS(ps_global
) = 3;
323 mark_status_unknown();
326 flush_status_messages(exit_val
? 0 : 1);
327 blank_keymenu(_lines
- 2, 0);
328 MoveCursor(_lines
- 2, 0);
332 if(pico_usingcolor())
335 if(_termcap_end
!= NULL
)
336 tputs(_termcap_end
, 1, outchar
);
341 printf("%s\r\n", message
);
344 if(F_ON(F_ENABLE_XTERM_NEWMAIL
, ps_global
) && getenv("DISPLAY"))
345 icon_text("xterm", IT_NEWMAIL
);
349 if(footer_rows_was_one
){
350 FOOTER_ROWS(ps_global
) = 1;
351 mark_status_unknown();
358 /*----------------------------------------------------------------------
359 Clear the terminal screen
361 Result: The screen is cleared
362 internal cursor position set to 0,0
367 _line
= 0; /* clear leaves us at top... */
370 if(ps_global
->in_init_seq
)
373 mark_status_unknown();
374 mark_keymenu_dirty();
375 mark_titlebar_dirty();
378 * If the terminal doesn't have back color erase, then we have to
379 * erase manually to preserve the background color.
381 if(pico_usingcolor() && (!_bce
|| !_clearscreen
)){
382 ClearLines(0, _lines
-1);
385 else if(_clearscreen
){
386 tputs(_clearscreen
, 1, outchar
);
387 moveabsolute(0, 0); /* some clearscreens don't move correctly */
392 /*----------------------------------------------------------------------
393 Internal move cursor to absolute position
395 Args: col -- column to move cursor to
396 row -- row to move cursor to
398 Result: cursor is moved (variables, not updates)
402 moveabsolute(int col
, int row
)
405 char *stuff
, *tgoto();
407 stuff
= tgoto(_moveto
, col
, row
);
408 tputs(stuff
, 1, outchar
);
412 /*----------------------------------------------------------------------
413 Move the cursor to the row and column number
418 internal position updated
421 MoveCursor(int row
, int col
)
423 /** move cursor to the specified row column on the screen.
424 0,0 is the top left! **/
428 /* we don't want to change "rows" or we'll mangle scrolling... */
430 if(ps_global
->in_init_seq
)
435 if (col
>= ps_global
->ttyo
->screen_cols
)
436 col
= ps_global
->ttyo
->screen_cols
- 1;
439 if (row
> ps_global
->ttyo
->screen_rows
) {
441 scrollafter
= row
- ps_global
->ttyo
->screen_rows
;
442 row
= ps_global
->ttyo
->screen_rows
;
448 if (_col
>= ps_global
->ttyo
->screen_cols
)
449 _col
= ps_global
->ttyo
->screen_cols
- 1;
453 return; /* already there! */
455 else if (abs(col
- _col
) < 5) { /* within 5 spaces... */
456 if (col
> _col
&& _right
)
457 CursorRight(col
- _col
);
458 else if (col
< _col
&& _left
)
459 CursorLeft(_col
- col
);
461 moveabsolute(col
, row
);
463 else /* move along to the new x,y loc */
464 moveabsolute(col
, row
);
466 else if (col
== _col
&& abs(row
- _line
) < 5) {
467 if (row
< _line
&& _up
)
468 CursorUp(_line
- row
);
469 else if (_line
> row
&& _down
)
470 CursorDown(row
- _line
);
472 moveabsolute(col
, row
);
474 else if (_line
== row
-1 && col
== 0) {
475 putchar('\n'); /* that's */
476 putchar('\r'); /* easy! */
479 moveabsolute(col
, row
);
481 _line
= row
; /* to ensure we're really there... */
485 while (scrollafter
--) {
497 /*----------------------------------------------------------------------
498 Newline, move the cursor to the start of next line
505 /** move the cursor to the beginning of the next line **/
513 /*----------------------------------------------------------------------
514 Move cursor up n lines with terminal escape sequence
516 Args: n -- number of lines to go up
518 Result: cursor moves,
519 internal position updated
521 Only for ttyout use; not outside callers
526 /** move the cursor up 'n' lines **/
527 /** Calling function must check that _up is not null before calling **/
529 _line
= (_line
-n
> 0? _line
- n
: 0); /* up 'n' lines... */
532 tputs(_up
, 1, outchar
);
537 /*----------------------------------------------------------------------
538 Move cursor down n lines with terminal escape sequence
540 Arg: n -- number of lines to go down
542 Result: cursor moves,
543 internal position updated
545 Only for ttyout use; not outside callers
550 /** move the cursor down 'n' lines **/
551 /** Caller must check that _down is not null before calling **/
553 _line
= (_line
+n
< ps_global
->ttyo
->screen_rows
? _line
+ n
554 : ps_global
->ttyo
->screen_rows
);
555 /* down 'n' lines... */
558 tputs(_down
, 1, outchar
);
563 /*----------------------------------------------------------------------
564 Move cursor left n lines with terminal escape sequence
566 Args: n -- number of lines to go left
568 Result: cursor moves,
569 internal position updated
571 Only for ttyout use; not outside callers
576 /** move the cursor 'n' characters to the left **/
577 /** Caller must check that _left is not null before calling **/
579 _col
= (_col
- n
> 0? _col
- n
: 0); /* left 'n' chars... */
582 tputs(_left
, 1, outchar
);
586 /*----------------------------------------------------------------------
587 Move cursor right n lines with terminal escape sequence
589 Args: number of lines to go right
591 Result: cursor moves,
592 internal position updated
594 Only for ttyout use; not outside callers
599 /** move the cursor 'n' characters to the right (nondestructive) **/
600 /** Caller must check that _right is not null before calling **/
602 _col
= (_col
+n
< ps_global
->ttyo
->screen_cols
? _col
+ n
:
603 ps_global
->ttyo
->screen_cols
); /* right 'n' chars... */
606 tputs(_right
, 1, outchar
);
611 /*----------------------------------------------------------------------
612 Go into scrolling mode, that is set scrolling region if applicable
614 Args: top -- top line of region to scroll
615 bottom -- bottom line of region to scroll
616 (These are zero-origin numbers)
618 Result: either set scrolling region or
619 save values for later scrolling
620 returns -1 if we can't scroll
622 Unfortunately this seems to leave the cursor in an unpredictable place
623 at least the manuals don't say where, so we force it here.
628 BeginScroll(int top
, int bottom
)
632 if(_scrollmode
== NoScroll
)
637 if(_scrollmode
== UseScrollRegion
){
638 stuff
= tgoto(_scrollregion
, bottom
, top
);
639 tputs(stuff
, 1, outchar
);
640 /*-- a location very far away to force a cursor address --*/
649 /*----------------------------------------------------------------------
650 End scrolling -- clear scrolling regions if necessary
652 Result: Clear scrolling region on terminal
657 if(_scrollmode
== UseScrollRegion
&& _scrollregion
!= NULL
){
658 /* Use tgoto even though we're not cursor addressing because
659 the format of the capability is the same.
661 char *stuff
= tgoto(_scrollregion
, ps_global
->ttyo
->screen_rows
-1, 0);
662 tputs(stuff
, 1, outchar
);
663 /*-- a location very far away to force a cursor address --*/
670 /* ----------------------------------------------------------------------
671 Scroll the screen using insert/delete or scrolling regions
673 Args: lines -- number of lines to scroll, positive forward
675 Result: Screen scrolls
676 returns 0 if scroll successful, -1 if not
678 positive lines goes forward (new lines come in at bottom
679 Leaves cursor at the place to insert put new text
681 0,0 is the upper left
684 ScrollRegion(int lines
)
691 if(_scrollmode
== UseScrollRegion
) {
694 for(l
= lines
; l
> 0 ; l
--)
695 tputs((_scrolldown
== NULL
|| _scrolldown
[0] =='\0') ? "\n" :
696 _scrolldown
, 1, outchar
);
699 for(l
= -lines
; l
> 0; l
--)
700 tputs(_scrollup
, 1, outchar
);
702 } else if(_scrollmode
== InsertDelete
) {
705 for(l
= lines
; l
> 0; l
--)
706 tputs(_deleteline
, 1, outchar
);
708 for(l
= lines
; l
> 0; l
--)
709 tputs(_insertline
, 1, outchar
);
711 for(l
= -lines
; l
> 0; l
--) {
713 tputs(_deleteline
, 1, outchar
);
715 tputs(_insertline
, 1, outchar
);
729 if(ps_global
->in_init_seq
/* silent */
730 || (F_ON(F_BLANK_KEYMENU
, ps_global
) /* or bottom, */
732 && _line
+ 1 == ps_global
->ttyo
->screen_rows
733 && _col
+ 1 == ps_global
->ttyo
->screen_cols
))
737 if(ucs
== LINE_FEED
|| ucs
== RETURN
|| ucs
== BACKSPACE
|| ucs
== BELL
|| ucs
== TAB
){
740 /*-- Don't have to watch out for auto wrap or newline glitch
741 because we never let it happen. See below
744 _line
= MIN(_line
+1,ps_global
->ttyo
->screen_rows
);
747 case RETURN
: /* move to column 0 */
752 case BACKSPACE
: /* move back a space if not in column 0 */
756 } /* else BACKSPACE does nothing */
760 case BELL
: /* ring the bell but don't advance _col */
764 case TAB
: /* if a tab, output it */
765 do /* BUG? ignores tty driver's spacing */
767 /* fix from Eduardo Chappa, have to increment _col once more */
768 while(_col
< ps_global
->ttyo
->screen_cols
769 && ((++_col
)&0x07) != 0);
774 unsigned char obuf
[MAX(MB_LEN_MAX
,32)];
775 int i
, width
= 0, outchars
= 0, printable_ascii
= 0;
777 if(ucs
< 0x80 && isprint((unsigned char) ucs
)){
778 printable_ascii
++; /* efficiency shortcut */
782 width
= wcellwidth(ucs
);
786 * This happens when we have a Unicode character that
787 * we aren't able to print in our locale. For example,
788 * if the locale is setup with the terminal
789 * expecting ISO-8859-1 characters then there are
790 * lots of Unicode characters that can't be printed.
791 * Print a '?' instead.
794 obuf
[outchars
++] = '?';
796 else if(_col
+ width
> ps_global
->ttyo
->screen_cols
){
798 * Hopefully this won't happen. The
799 * character overflows the right edge because it
800 * is a double wide and we're in last column.
801 * Fill with spaces instead and toss the
804 * Put a > in the right column to show we tried to write
807 while(outchars
< ps_global
->ttyo
->screen_cols
- _col
- 1)
808 obuf
[outchars
++] = ' ';
810 obuf
[outchars
++] = '>';
813 * In case last time through wrote in the last column and
814 * caused an autowrap, reposition cursor.
816 if(_col
>= ps_global
->ttyo
->screen_cols
)
817 moveabsolute(ps_global
->ttyo
->screen_cols
-1, _line
);
821 * Convert the ucs into the multibyte
822 * character that corresponds to the
823 * ucs in the users locale.
826 obuf
[outchars
++] = (unsigned char) ucs
;
828 outchars
= wtomb((char *) obuf
, ucs
);
838 for(i
= 0; i
< outchars
; i
++)
843 * We used to wrap by moving to the next line and making _col = 0
844 * when we went past the end. We don't believe that this is useful
845 * anymore. If we wrap it is unintentional. Instead, stay at the
846 * end of the line. We need to be a little careful because we're
847 * moving to a different place than _col is set to, but since _col
848 * is off the right edge, it should be ok.
850 if(_col
>= ps_global
->ttyo
->screen_cols
) {
851 _col
= ps_global
->ttyo
->screen_cols
;
852 moveabsolute(ps_global
->ttyo
->screen_cols
-1, _line
);
858 Write_to_screen(char *string
) /* UNIX */
862 Writechar((unsigned char) *string
++, 0);
866 /*----------------------------------------------------------------------
867 Write no more than n chars of string to screen at current cursor position
869 Args: string -- string to be output
870 n -- number of chars to output
872 Result: String written to the screen
875 Write_to_screen_n(char *string
, int n
) /* UNIX */
879 while(n
-- && *string
)
880 Writechar((unsigned char) *string
++, 0);
884 /*----------------------------------------------------------------------
885 Clear screen to end of line on current line
887 Result: Line is cleared
892 int c
, starting_col
, starting_line
;
896 * If the terminal doesn't have back color erase, then we have to
897 * erase manually to preserve the background color.
899 if(pico_usingcolor() && (!_bce
|| !_cleartoeoln
)){
901 starting_line
= _line
;
902 last_bg_color
= pico_get_last_bg_color();
903 pico_set_nbg_color();
904 for(c
= _col
; c
< _columns
; c
++)
907 MoveCursor(starting_line
, starting_col
);
909 (void)pico_set_bg_color(last_bg_color
);
910 fs_give((void **)&last_bg_color
);
913 else if(_cleartoeoln
)
914 tputs(_cleartoeoln
, 1, outchar
);
919 /*----------------------------------------------------------------------
920 Clear screen to end of screen from current point
922 Result: screen is cleared
927 int starting_col
, starting_line
;
930 * If the terminal doesn't have back color erase, then we have to
931 * erase manually to preserve the background color.
933 if(pico_usingcolor() && (!_bce
|| !_cleartoeos
)){
935 starting_line
= _line
;
937 ClearLines(_line
+1, _lines
-1);
938 MoveCursor(starting_line
, starting_col
);
941 tputs(_cleartoeos
, 1, outchar
);
946 /*----------------------------------------------------------------------
947 function to output character used by termcap
949 Args: c -- character to output
951 Result: character output to screen via stdio
956 /** output the given character. From tputs... **/
957 /** Note: this CANNOT be a macro! **/
959 return(putc((unsigned char)c
, stdout
));
964 /*----------------------------------------------------------------------
965 function to output string such that it becomes icon text
967 Args: s -- string to write
969 Result: string indicated become our "icon" text
972 icon_text(char *s
, int type
)
974 static enum {ukn
, yes
, no
} xterm
;
975 char *str
, *converted
, *use_this
;
978 xterm
= (getenv("DISPLAY") != NULL
) ? yes
: no
;
980 if(F_ON(F_ENABLE_XTERM_NEWMAIL
,ps_global
) && xterm
== yes
){
981 fputs("\033]0;", stdout
);
983 str
= s
? s
: ps_global
->pine_name
;
984 converted
= convert_to_locale(str
);
985 use_this
= converted
? converted
: str
;
987 fputs(use_this
, stdout
);
989 fputs("\007", stdout
);
990 fputs("\033]1;", stdout
);
992 fputs(use_this
, stdout
);
994 fputs("\007", stdout
);
998 fs_give((void **) &converted
);