1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: termin.unx.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2017 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/charconv/utf8.h"
29 #include "../../pith/charconv/filesys.h"
31 #include "../../pith/osdep/color.h"
32 #include "../../pith/osdep/collate.h"
33 #include "../../pith/osdep/err_desc.h"
35 #include "../../pith/debug.h"
36 #include "../../pith/state.h"
37 #include "../../pith/conf.h"
38 #include "../../pith/detach.h"
39 #include "../../pith/adrbklib.h"
40 #include "../../pith/remote.h"
41 #include "../../pith/imap.h"
42 #include "../../pith/status.h"
44 #include "../pico/estruct.h"
46 #include "../../pico/estruct.h"
47 #include "../../pico/pico.h"
48 #include "../../pico/osdep/raw.h"
49 #include "../../pico/osdep/signals.h"
50 #include "../../pico/osdep/mouse.h"
51 #include "../../pico/osdep/read.h"
52 #include "../../pico/osdep/getkey.h"
53 #include "../../pico/osdep/tty.h"
54 #include "../../pico/keydefs.h"
58 #include "../dispfilt.h"
59 #include "../signal.h"
60 #include "../mailcmd.h"
63 #include "termin.gen.h"
64 #include "termout.gen.h"
65 #include "termin.unx.h"
69 /*======================================================================
70 Things having to do with reading from the tty driver and keyboard
71 - initialize tty driver and reset tty driver
72 - read a character from terminal with keyboard escape seqence mapping
73 - initialize keyboard (keypad or such) and reset keyboard
74 - prompt user for a line of input
75 - read a command from keyboard with timeouts.
84 * Should really be using pico's TERM's t_getchar to read a character but
85 * we're just calling ttgetc directly for now. Ttgetc is the same as
86 * t_getchar whenever we use it so we're avoiding the trouble of initializing
87 * the TERM struct and calling ttgetc directly.
89 #define READ_A_CHAR() ttgetc(NO_OP_COMMAND, key_rec, read_bail)
95 int pine_simple_ttgetc(int (*recorder
)(int), void (*bail_handler
)(void));
96 UCS
check_for_timeout(int);
100 /*----------------------------------------------------------------------
101 Initialize the tty driver to do single char I/O and whatever else (UNIX)
105 Result: tty driver is put in raw mode so characters can be read one
106 at a time. Returns -1 if unsuccessful, 0 if successful.
108 Some file descriptor voodoo to allow for pipes across vforks. See
109 open_mailer for details.
110 ----------------------------------------------------------------------*/
112 init_tty_driver(struct pine
*ps
)
115 if(F_ON(F_ENABLE_MOUSE
, ps_global
))
119 /* turn off talk permission by default */
121 if(F_ON(F_ALLOW_TALK
, ps
))
131 /*----------------------------------------------------------------------
132 Set or clear the specified tty mode
134 Args: ps -- struct pine
135 mode -- mode bits to modify
136 clear -- whether or not to clear or set
138 Result: tty driver mode change.
139 ----------------------------------------------------------------------*/
141 tty_chmod(struct pine
*ps
, int mode
, int func
)
146 static int saved_mode
= -1;
148 /* if no problem figuring out tty's name & mode? */
149 if((((tty_name
= (char *) ttyname(STDIN_FD
)) != NULL
150 && fstat(STDIN_FD
, &sbuf
) == 0)
151 || ((tty_name
= (char *) ttyname(STDOUT_FD
)) != NULL
152 && fstat(STDOUT_FD
, &sbuf
) == 0))
153 && !(func
== TMD_RESET
&& saved_mode
< 0)){
154 new_mode
= (func
== TMD_RESET
)
156 : (func
== TMD_CLEAR
)
157 ? (sbuf
.st_mode
& ~mode
)
158 : (sbuf
.st_mode
| mode
);
159 /* assign tty new mode */
160 if(our_chmod(tty_name
, new_mode
) == 0){
161 if(func
== TMD_RESET
) /* forget we knew */
163 else if(saved_mode
< 0)
164 saved_mode
= sbuf
.st_mode
; /* remember original */
171 /*----------------------------------------------------------------------
172 End use of the tty, put it back into it's normal mode (UNIX)
174 Args: ps -- struct pine
176 Result: tty driver mode change.
177 ----------------------------------------------------------------------*/
179 end_tty_driver(struct pine
*ps
)
181 ps
= ps
; /* get rid of unused parameter warning */
187 dprint((2, "about to end_tty_driver\n"));
189 tty_chmod(ps
, 0, TMD_RESET
);
195 /*----------------------------------------------------------------------
196 Actually set up the tty driver (UNIX)
198 Args: state -- which state to put it in. 1 means go into raw, 0 out of
200 Result: returns 0 if successful and < 0 if not.
209 if(result
== 0 && state
== 1){
211 * Only go into 8 bit mode if we are doing something other
212 * than plain ASCII. This will save the folks that have
213 * their parity on their serial lines wrong the trouble of
216 if((ps_global
->keyboard_charmap
&& ps_global
->keyboard_charmap
[0] &&
217 strucmp(ps_global
->keyboard_charmap
, "us-ascii"))
218 || (ps_global
->display_charmap
&& ps_global
->display_charmap
[0] &&
219 strucmp(ps_global
->display_charmap
, "us-ascii")))
223 if(debug
< 9) /* only on if full debugging set */
226 ps_global
->low_speed
= ttisslow();
228 xonxoff_proc(F_ON(F_PRESERVE_START_STOP
, ps_global
));
235 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
237 int winch_occured
= 0;
238 int ready_for_winch
= 0;
241 /*----------------------------------------------------------------------
242 This checks whether or not a character (UNIX)
243 is ready to be read, or it times out.
245 Args: time_out -- number of seconds before it will timeout
247 Result: Returns a NO_OP_IDLE or a NO_OP_COMMAND if the timeout expires
248 before input is available, or a KEY_RESIZE if a resize event
249 occurs, or READY_TO_READ if input is available before the timeout.
252 check_for_timeout(int time_out
)
254 UCS res
= NO_OP_COMMAND
;
258 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
260 if(setjmp(winch_state
) != 0){
265 * Need to unblock signal after longjmp from handler, because
266 * signal is normally unblocked upon routine exit from the handler.
268 our_sigunblock(SIGWINCH
);
275 winch_occured
= ready_for_winch
= 0;
276 fix_windsize(ps_global
);
279 #endif /* SIGWINCH */
281 switch(res
= input_ready(time_out
)){
283 read_bail(); /* non-tragic exit */
287 panic1("Select error: %s\n", error_description(errno
));
297 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
309 /*----------------------------------------------------------------------
310 Read input characters with lots of processing for arrow keys and such (UNIX)
312 Args: time_out -- The timeout to for the reads
314 Result: returns the character read. Possible special chars.
316 This deals with function and arrow keys as well.
318 The idea is that this routine handles all escape codes so it done in
319 only one place. Especially so the back arrow key can work when entering
320 things on a line. Also so all function keys can be disabled and not
321 cause weird things to happen.
324 read_char(int time_out
)
329 key_rec
= key_recorder
;
330 if(ps_global
->conceal_sensitive_debugging
)
333 /* Get input from initial-keystrokes */
334 if(process_config_input(&cc
)){
339 if((ch
= check_for_timeout(time_out
)) != READY_TO_READ
)
342 switch(status
= kbseq(pine_simple_ttgetc
, key_rec
, read_bail
,
343 ps_global
->input_cs
, &ch
)){
346 * Special hack to get around comm devices eating control characters.
348 if(check_for_timeout(5) != READY_TO_READ
){
349 dprint((9, "Read char: incomplete double escape timed out...\n"));
350 ch
= KEY_JUNK
; /* user typed ESC ESC, then stopped */
358 /* We allow a 3-digit number between 001 and 255 */
359 if(isdigit((unsigned char) ch
)){
360 int n
= 0, i
= ch
- '0';
363 dprint((9, "Read char: double escape followed by 1st digit not 0, 1, or 2... (%d)\n", i
));
365 goto done
; /* bogus literal char value */
369 if(check_for_timeout(5) != READY_TO_READ
370 || (!isdigit((unsigned char) (ch
= READ_A_CHAR()))
371 || (n
== 1 && i
== 2 && ch
> '5')
372 || (n
== 2 && i
== 25 && ch
> '5'))){
373 dprint((9, "Read char: bad double escape, either timed out or too large 3-digit num...\n"));
374 ch
= KEY_JUNK
; /* user typed ESC ESC #, stopped */
378 i
= (i
* 10) + (ch
- '0');
383 else{ /* or, normal case, ESC ESC c means ^c */
384 if(islower((unsigned char) ch
)) /* canonicalize if alpha */
385 ch
= toupper((unsigned char) ch
);
387 ch
= (isalpha((unsigned char)ch
) || ch
== '@'
388 || (ch
>= '[' && ch
<= '_'))
389 ? ctrl(ch
) : ((ch
== SPACE
) ? ctrl('@'): ch
);
390 dprint((9, "Read char: this is a successful double escape...\n"));
396 case KEY_XTERM_MOUSE
:
399 * Special hack to get mouse events from an xterm.
400 * Get the details, then pass it past the keymenu event
401 * handler, and then to the installed handler if there
409 button
= READ_A_CHAR() & 0x03;
411 x
= READ_A_CHAR() - '!';
412 y
= READ_A_CHAR() - '!';
415 if(button
== 0){ /* xterm button 1 down */
417 if(checkmouse(&cmd
, 1, x
, y
))
420 else if (down
&& button
== 3){
422 if(checkmouse(&cmd
, 0, x
, y
))
453 dprint((9, "Read char returning: 0x%x %s\n", status
, pretty_command(status
)));
458 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_UP)\n", status
, pretty_command(status
)));
463 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_DOWN)\n", status
, pretty_command(status
)));
466 case CTRL_KEY_RIGHT
:
468 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_RIGHT)\n", status
, pretty_command(status
)));
473 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_LEFT)\n", status
, pretty_command(status
)));
483 if(check_for_timeout(2) != READY_TO_READ
){
487 while(!strchr("~qz", READ_A_CHAR()));
488 ch
= (status
== KEY_JUNK
) ? status
: status
- (KEY_SWAL_UP
- KEY_UP
);
494 if(check_for_timeout(2) != READY_TO_READ
){
500 }while(cc
!= '\033' && ch
!= '\\');
509 case 0: /* regular character */
512 * we used to strip (ch &= 0x7f;), but this seems much cleaner
513 * in the face of line noise and has the benefit of making it
514 * tougher to emit mistakenly labeled MIME...
517 && ((!ps_global
->keyboard_charmap
|| !strucmp(ps_global
->keyboard_charmap
, "US-ASCII"))
518 && (!ps_global
->display_charmap
|| !strucmp(ps_global
->display_charmap
, "US-ASCII")))){
519 dprint((9, "Read char sees ch = 0x%x status=0x%x, returns KEY_JUNK\n", ch
, status
));
522 else if(ch
== ctrl('Z')){
523 dprint((9, "Read char got ^Z, calling do_suspend\n"));
525 dprint((9, "After do_suspend Read char returns 0x%x %s\n", ch
, pretty_command(ch
)));
529 else if(ch
== ctrl('\\')){
532 dprint((9, "Read char got ^\\, toggle xterm mouse\n"));
533 if(F_ON(F_ENABLE_MOUSE
, ps_global
)){
534 (e
=mouseexist()) ? end_mouse() : (void) init_mouse();
535 if(e
!= mouseexist())
536 q_status_message1(SM_ASYNC
, 0, 2, "Xterm mouse tracking %s!",
537 mouseexist() ? "on" : "off");
539 q_status_message1(SM_ASYNC
, 0, 2, "See help for feature \"%s\" ($DISPLAY variable set?)", pretty_feature_name(feature_list_name(F_ENABLE_MOUSE
), -1));
542 q_status_message1(SM_ASYNC
, 0, 2, "Feature \"%s\" not enabled",
543 pretty_feature_name(feature_list_name(F_ENABLE_MOUSE
), -1));
545 return(NO_OP_COMMAND
);
552 if(ps_global
->conceal_sensitive_debugging
&& debug
< 10){
553 dprint((9, "Read char returning: <hidden char>\n"));
556 dprint((9, "Read char returning: 0x%x %s\n", ch
, pretty_command(ch
)));
568 /*----------------------------------------------------------------------
569 Reading input somehow failed and we need to shutdown now
579 dprint((1, "read_bail: cleaning up\n"));
581 /* Do not bail out on a tcp timeout, instead close the troublesome stream */
582 if(ps_global
->tcptimeout
&& some_stream_is_locked()){
583 ps_global
->read_bail
= 1;
589 * This gets rid of temporary cache files for remote addrbooks.
591 completely_done_with_adrbks();
594 * This flushes out deferred changes and gets rid of temporary cache
595 * files for remote config files.
598 if(ps_global
->prc
->outstanding_pinerc_changes
)
599 write_pinerc(ps_global
, Main
, WRP_NOUSER
);
601 if(ps_global
->prc
->rd
)
602 rd_close_remdata(&ps_global
->prc
->rd
);
604 free_pinerc_s(&ps_global
->prc
);
608 if(ps_global
->post_prc
){
609 if(ps_global
->post_prc
->outstanding_pinerc_changes
)
610 write_pinerc(ps_global
, Post
, WRP_NOUSER
);
612 if(ps_global
->post_prc
->rd
)
613 rd_close_remdata(&ps_global
->post_prc
->rd
);
615 free_pinerc_s(&ps_global
->post_prc
);
620 dprint((1, "done with read_bail clean up\n"));
622 imap_flush_passwd_cache(TRUE
);
623 end_keyboard(F_ON(F_USE_FK
,ps_global
));
624 end_tty_driver(ps_global
);
625 if(filter_data_file(0))
626 our_unlink(filter_data_file(0));
633 pine_simple_ttgetc(int (*fi
)(int), void (*fv
)(void))
637 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
639 if(setjmp(winch_state
) != 0){
644 * Need to unblock signal after longjmp from handler, because
645 * signal is normally unblocked upon routine exit from the handler.
647 our_sigunblock(SIGWINCH
);
654 winch_occured
= ready_for_winch
= 0;
655 fix_windsize(ps_global
);
658 #endif /* SIGWINCH */
660 ch
= simple_ttgetc(fi
, fv
);
662 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
671 extern char term_name
[];
672 /* -------------------------------------------------------------------
673 Set up the keyboard -- usually enable some function keys (UNIX)
677 So far all we do here is turn on keypad mode for certain terminals
679 Hack for NCSA telnet on an IBM PC to put the keypad in the right mode.
680 This is the same for a vtXXX terminal or [zh][12]9's which we have
684 init_keyboard(int use_fkeys
)
686 if(use_fkeys
&& (!strucmp(term_name
,"vt102")
687 || !strucmp(term_name
,"vt100")))
688 printf("\033\133\071\071\150");
693 /*----------------------------------------------------------------------
694 Clear keyboard, usually disable some function keys (UNIX)
696 Args: pine state (terminal type)
698 Result: keyboard state reset
701 end_keyboard(int use_fkeys
)
703 if(use_fkeys
&& (!strcmp(term_name
, "vt102")
704 || !strcmp(term_name
, "vt100"))){
705 printf("\033\133\071\071\154");
712 * This is a bare-bones implementation which is missing most of the
713 * features of the real optionally_enter. The initial value of string is
714 * isgnored. The escape_list is ignored. The help is not implemented. The
715 * only flag implemented is OE_PASSWD. We don't go into raw mode so the
716 * only input possible is a line (the EOL is stripped before returning).
719 pre_screen_config_opt_enter(char *string
, int string_size
, char *prompt
,
720 ESCKEY_S
*escape_list
, HelpType help
, int *flags
)
725 while(return_v
== -10) {
727 if(flags
&& (*flags
& (OE_PASSWD
| OE_PASSWD_NOAST
))){
728 if((pw
= getpass(prompt
)) != NULL
){
729 if(strlen(pw
) < string_size
){
730 strncpy(string
, pw
, string_size
);
731 string
[string_size
-1] = '\0';
735 if(fputs("Password too long\n", stderr
) != EOF
)
738 alpine_panic(_("error on fputs() call!"));
742 return_v
= 1; /* cancel? */
747 if(fputs(prompt
, stdout
) != EOF
748 && fgets(string
, string_size
, stdin
) != NULL
){
749 string
[string_size
-1] = '\0';
750 if((p
= strpbrk(string
, "\r\n")) != NULL
)
755 alpine_panic(_("error on fputs() or fgets() call!"));