* New version 2.21
[alpine.git] / alpine / osdep / termin.unx.c
blob29035bbefd0a0b4677f4debd59299431bd3650fe
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 $";
3 #endif
5 /*
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 * ========================================================================
18 #include <system.h>
19 #include <general.h>
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"
56 #include "../talk.h"
57 #include "../radio.h"
58 #include "../dispfilt.h"
59 #include "../signal.h"
60 #include "../mailcmd.h"
61 #include "../setup.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.
77 ====*/
81 * Helpful definitions
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)
93 * Internal prototypes
95 int pine_simple_ttgetc(int (*recorder)(int), void (*bail_handler)(void));
96 UCS check_for_timeout(int);
97 void read_bail(void);
100 /*----------------------------------------------------------------------
101 Initialize the tty driver to do single char I/O and whatever else (UNIX)
103 Args: struct pine
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)
114 #ifdef MOUSE
115 if(F_ON(F_ENABLE_MOUSE, ps_global))
116 init_mouse();
117 #endif /* MOUSE */
119 /* turn off talk permission by default */
121 if(F_ON(F_ALLOW_TALK, ps))
122 allow_talk(ps);
123 else
124 disallow_talk(ps);
126 return(PineRaw(1));
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 ----------------------------------------------------------------------*/
140 void
141 tty_chmod(struct pine *ps, int mode, int func)
143 char *tty_name;
144 int new_mode;
145 struct stat sbuf;
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)
155 ? saved_mode
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 */
162 saved_mode = -1;
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 ----------------------------------------------------------------------*/
178 void
179 end_tty_driver(struct pine *ps)
181 ps = ps; /* get rid of unused parameter warning */
183 #ifdef MOUSE
184 end_mouse();
185 #endif /* MOUSE */
186 fflush(stdout);
187 dprint((2, "about to end_tty_driver\n"));
189 tty_chmod(ps, 0, TMD_RESET);
190 PineRaw(0);
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.
201 ----*/
203 PineRaw(int state)
205 int result;
207 result = Raw(state);
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
214 * getting it right
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")))
220 bit_strip_off();
222 #ifdef DEBUG
223 if(debug < 9) /* only on if full debugging set */
224 #endif
225 quit_char_off();
226 ps_global->low_speed = ttisslow();
227 crlf_proc(0);
228 xonxoff_proc(F_ON(F_PRESERVE_START_STOP, ps_global));
231 return(result);
235 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
236 jmp_buf winch_state;
237 int winch_occured = 0;
238 int ready_for_winch = 0;
239 #endif
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.
250 ----*/
252 check_for_timeout(int time_out)
254 UCS res = NO_OP_COMMAND;
256 fflush(stdout);
258 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
259 if(!winch_occured){
260 if(setjmp(winch_state) != 0){
261 winch_occured = 1;
262 ready_for_winch = 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);
270 else
271 ready_for_winch = 1;
274 if(winch_occured){
275 winch_occured = ready_for_winch = 0;
276 fix_windsize(ps_global);
277 return(KEY_RESIZE);
279 #endif /* SIGWINCH */
281 switch(res = input_ready(time_out)){
282 case BAIL_OUT:
283 read_bail(); /* non-tragic exit */
284 /* NO RETURN */
286 case PANIC_NOW:
287 panic1("Select error: %s\n", error_description(errno));
288 /* NO RETURN */
290 case READ_INTR:
291 res = NO_OP_COMMAND;
292 /* fall through */
294 case NO_OP_IDLE:
295 case NO_OP_COMMAND:
296 case READY_TO_READ:
297 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
298 ready_for_winch = 0;
299 #endif
300 return(res);
303 /* not reachable */
304 return(res);
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.
322 ---*/
324 read_char(int time_out)
326 UCS status, cc, ch;
327 int (*key_rec)(int);
329 key_rec = key_recorder;
330 if(ps_global->conceal_sensitive_debugging)
331 key_rec = NULL;
333 /* Get input from initial-keystrokes */
334 if(process_config_input(&cc)){
335 ch = cc;
336 return(ch);
339 if((ch = check_for_timeout(time_out)) != READY_TO_READ)
340 goto done;
342 switch(status = kbseq(pine_simple_ttgetc, key_rec, read_bail,
343 ps_global->input_cs, &ch)){
344 case KEY_DOUBLE_ESC:
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 */
351 goto done;
353 else
354 ch = READ_A_CHAR();
356 ch &= 0x7f;
358 /* We allow a 3-digit number between 001 and 255 */
359 if(isdigit((unsigned char) ch)){
360 int n = 0, i = ch - '0';
362 if(i < 0 || i > 2){
363 dprint((9, "Read char: double escape followed by 1st digit not 0, 1, or 2... (%d)\n", i));
364 ch = KEY_JUNK;
365 goto done; /* bogus literal char value */
368 while(n++ < 2){
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 */
375 goto done;
378 i = (i * 10) + (ch - '0');
381 ch = i;
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"));
393 goto done;
395 #ifdef MOUSE
396 case KEY_XTERM_MOUSE:
397 if(mouseexist()){
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
402 * is one...
404 static int down = 0;
405 int x, y, button;
406 unsigned long cmd;
408 clear_cursor_pos();
409 button = READ_A_CHAR() & 0x03;
411 x = READ_A_CHAR() - '!';
412 y = READ_A_CHAR() - '!';
414 ch = NO_OP_COMMAND;
415 if(button == 0){ /* xterm button 1 down */
416 down = 1;
417 if(checkmouse(&cmd, 1, x, y))
418 ch = cmd;
420 else if (down && button == 3){
421 down = 0;
422 if(checkmouse(&cmd, 0, x, y))
423 ch = cmd;
426 goto done;
429 break;
430 #endif /* MOUSE */
432 case KEY_UP :
433 case KEY_DOWN :
434 case KEY_RIGHT :
435 case KEY_LEFT :
436 case KEY_PGUP :
437 case KEY_PGDN :
438 case KEY_HOME :
439 case KEY_END :
440 case KEY_DEL :
441 case PF1 :
442 case PF2 :
443 case PF3 :
444 case PF4 :
445 case PF5 :
446 case PF6 :
447 case PF7 :
448 case PF8 :
449 case PF9 :
450 case PF10 :
451 case PF11 :
452 case PF12 :
453 dprint((9, "Read char returning: 0x%x %s\n", status, pretty_command(status)));
454 return(status);
456 case CTRL_KEY_UP :
457 status = KEY_UP;
458 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_UP)\n", status, pretty_command(status)));
459 return(status);
461 case CTRL_KEY_DOWN :
462 status = KEY_DOWN;
463 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_DOWN)\n", status, pretty_command(status)));
464 return(status);
466 case CTRL_KEY_RIGHT :
467 status = KEY_RIGHT;
468 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_RIGHT)\n", status, pretty_command(status)));
469 return(status);
471 case CTRL_KEY_LEFT :
472 status = KEY_LEFT;
473 dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_LEFT)\n", status, pretty_command(status)));
474 return(status);
476 case KEY_SWALLOW_Z:
477 status = KEY_JUNK;
478 case KEY_SWAL_UP:
479 case KEY_SWAL_DOWN:
480 case KEY_SWAL_LEFT:
481 case KEY_SWAL_RIGHT:
483 if(check_for_timeout(2) != READY_TO_READ){
484 status = KEY_JUNK;
485 break;
487 while(!strchr("~qz", READ_A_CHAR()));
488 ch = (status == KEY_JUNK) ? status : status - (KEY_SWAL_UP - KEY_UP);
489 goto done;
491 case KEY_KERMIT:
493 cc = ch;
494 if(check_for_timeout(2) != READY_TO_READ){
495 status = KEY_JUNK;
496 break;
498 else
499 ch = READ_A_CHAR();
500 }while(cc != '\033' && ch != '\\');
502 ch = KEY_JUNK;
503 goto done;
505 case BADESC:
506 ch = KEY_JUNK;
507 goto done;
509 case 0: /* regular character */
510 default:
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...
516 if((ch & ~0x7f)
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));
520 return(KEY_JUNK);
522 else if(ch == ctrl('Z')){
523 dprint((9, "Read char got ^Z, calling do_suspend\n"));
524 ch = do_suspend();
525 dprint((9, "After do_suspend Read char returns 0x%x %s\n", ch, pretty_command(ch)));
526 return(ch);
528 #ifdef MOUSE
529 else if(ch == ctrl('\\')){
530 int e;
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");
538 else if(!e)
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));
541 else
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);
547 #endif /* MOUSE */
550 done:
551 #ifdef DEBUG
552 if(ps_global->conceal_sensitive_debugging && debug < 10){
553 dprint((9, "Read char returning: <hidden char>\n"));
555 else{
556 dprint((9, "Read char returning: 0x%x %s\n", ch, pretty_command(ch)));
558 #endif
560 return(ch);
563 /* not reachable */
564 return(KEY_JUNK);
568 /*----------------------------------------------------------------------
569 Reading input somehow failed and we need to shutdown now
571 Args: none
573 Result: pine exits
575 ---*/
576 void
577 read_bail(void)
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;
584 return;
586 end_signals(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.
597 if(ps_global->prc){
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);
607 /* as does this */
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);
618 sp_end();
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));
628 exit(0);
633 pine_simple_ttgetc(int (*fi)(int), void (*fv)(void))
635 int ch;
637 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
638 if(!winch_occured){
639 if(setjmp(winch_state) != 0){
640 winch_occured = 1;
641 ready_for_winch = 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);
649 else
650 ready_for_winch = 1;
653 if(winch_occured){
654 winch_occured = ready_for_winch = 0;
655 fix_windsize(ps_global);
656 return(KEY_RESIZE);
658 #endif /* SIGWINCH */
660 ch = simple_ttgetc(fi, fv);
662 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
663 ready_for_winch = 0;
664 #endif
666 return(ch);
671 extern char term_name[];
672 /* -------------------------------------------------------------------
673 Set up the keyboard -- usually enable some function keys (UNIX)
675 Args: struct pine
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
681 a lot of at UW
682 ----*/
683 void
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
699 ----*/
700 void
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");
706 fflush(stdout);
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)
722 char *pw;
723 int return_v = -10;
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';
732 return_v = 0;
734 else{
735 if(fputs("Password too long\n", stderr) != EOF)
736 return_v = -1;
737 else
738 alpine_panic(_("error on fputs() call!"));
741 else
742 return_v = 1; /* cancel? */
744 else{
745 char *p;
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)
751 *p = '\0';
752 return_v = 0;
754 else
755 alpine_panic(_("error on fputs() or fgets() call!"));
759 return(return_v);