diff options
Diffstat (limited to 'alpine/osdep/termin.unx.c')
-rw-r--r-- | alpine/osdep/termin.unx.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/alpine/osdep/termin.unx.c b/alpine/osdep/termin.unx.c new file mode 100644 index 00000000..451d18c2 --- /dev/null +++ b/alpine/osdep/termin.unx.c @@ -0,0 +1,752 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: termin.unx.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2008 University of Washington + * Copyright 2013 Eduardo Chappa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * ======================================================================== + */ +#include <system.h> +#include <general.h> + +#include "../../c-client/mail.h" /* for MAILSTREAM and friends */ +#include "../../c-client/osdep.h" +#include "../../c-client/rfc822.h" /* for soutr_t and such */ +#include "../../c-client/misc.h" /* for cpystr proto */ +#include "../../c-client/utf8.h" /* for CHARSET and such*/ +#include "../../c-client/imap4r1.h" + +#include "../../pith/charconv/utf8.h" +#include "../../pith/charconv/filesys.h" + +#include "../../pith/osdep/color.h" +#include "../../pith/osdep/collate.h" +#include "../../pith/osdep/err_desc.h" + +#include "../../pith/debug.h" +#include "../../pith/state.h" +#include "../../pith/conf.h" +#include "../../pith/detach.h" +#include "../../pith/adrbklib.h" +#include "../../pith/remote.h" +#include "../../pith/imap.h" +#include "../../pith/status.h" + +#include "../pico/estruct.h" + +#include "../../pico/estruct.h" +#include "../../pico/pico.h" +#include "../../pico/osdep/raw.h" +#include "../../pico/osdep/signals.h" +#include "../../pico/osdep/mouse.h" +#include "../../pico/osdep/read.h" +#include "../../pico/osdep/getkey.h" +#include "../../pico/osdep/tty.h" +#include "../../pico/keydefs.h" + +#include "../talk.h" +#include "../radio.h" +#include "../dispfilt.h" +#include "../signal.h" +#include "../mailcmd.h" +#include "../setup.h" + +#include "termin.gen.h" +#include "termout.gen.h" +#include "termin.unx.h" + + + +/*====================================================================== + Things having to do with reading from the tty driver and keyboard + - initialize tty driver and reset tty driver + - read a character from terminal with keyboard escape seqence mapping + - initialize keyboard (keypad or such) and reset keyboard + - prompt user for a line of input + - read a command from keyboard with timeouts. + + ====*/ + + +/* + * Helpful definitions + */ +/* + * Should really be using pico's TERM's t_getchar to read a character but + * we're just calling ttgetc directly for now. Ttgetc is the same as + * t_getchar whenever we use it so we're avoiding the trouble of initializing + * the TERM struct and calling ttgetc directly. + */ +#define READ_A_CHAR() ttgetc(NO_OP_COMMAND, key_rec, read_bail) + + +/* + * Internal prototypes + */ +int pine_simple_ttgetc(int (*recorder)(int), void (*bail_handler)(void)); +UCS check_for_timeout(int); +void read_bail(void); + + +/*---------------------------------------------------------------------- + Initialize the tty driver to do single char I/O and whatever else (UNIX) + + Args: struct pine + + Result: tty driver is put in raw mode so characters can be read one + at a time. Returns -1 if unsuccessful, 0 if successful. + +Some file descriptor voodoo to allow for pipes across vforks. See +open_mailer for details. + ----------------------------------------------------------------------*/ +int +init_tty_driver(struct pine *ps) +{ +#ifdef MOUSE + if(F_ON(F_ENABLE_MOUSE, ps_global)) + init_mouse(); +#endif /* MOUSE */ + + /* turn off talk permission by default */ + + if(F_ON(F_ALLOW_TALK, ps)) + allow_talk(ps); + else + disallow_talk(ps); + + return(PineRaw(1)); +} + + + +/*---------------------------------------------------------------------- + Set or clear the specified tty mode + + Args: ps -- struct pine + mode -- mode bits to modify + clear -- whether or not to clear or set + + Result: tty driver mode change. + ----------------------------------------------------------------------*/ +void +tty_chmod(struct pine *ps, int mode, int func) +{ + char *tty_name; + int new_mode; + struct stat sbuf; + static int saved_mode = -1; + + /* if no problem figuring out tty's name & mode? */ + if((((tty_name = (char *) ttyname(STDIN_FD)) != NULL + && fstat(STDIN_FD, &sbuf) == 0) + || ((tty_name = (char *) ttyname(STDOUT_FD)) != NULL + && fstat(STDOUT_FD, &sbuf) == 0)) + && !(func == TMD_RESET && saved_mode < 0)){ + new_mode = (func == TMD_RESET) + ? saved_mode + : (func == TMD_CLEAR) + ? (sbuf.st_mode & ~mode) + : (sbuf.st_mode | mode); + /* assign tty new mode */ + if(our_chmod(tty_name, new_mode) == 0){ + if(func == TMD_RESET) /* forget we knew */ + saved_mode = -1; + else if(saved_mode < 0) + saved_mode = sbuf.st_mode; /* remember original */ + } + } +} + + + +/*---------------------------------------------------------------------- + End use of the tty, put it back into it's normal mode (UNIX) + + Args: ps -- struct pine + + Result: tty driver mode change. + ----------------------------------------------------------------------*/ +void +end_tty_driver(struct pine *ps) +{ + ps = ps; /* get rid of unused parameter warning */ + +#ifdef MOUSE + end_mouse(); +#endif /* MOUSE */ + fflush(stdout); + dprint((2, "about to end_tty_driver\n")); + + tty_chmod(ps, 0, TMD_RESET); + PineRaw(0); +} + + + +/*---------------------------------------------------------------------- + Actually set up the tty driver (UNIX) + + Args: state -- which state to put it in. 1 means go into raw, 0 out of + + Result: returns 0 if successful and < 0 if not. + ----*/ +int +PineRaw(int state) +{ + int result; + + result = Raw(state); + + if(result == 0 && state == 1){ + /* + * Only go into 8 bit mode if we are doing something other + * than plain ASCII. This will save the folks that have + * their parity on their serial lines wrong the trouble of + * getting it right + */ + if((ps_global->keyboard_charmap && ps_global->keyboard_charmap[0] && + strucmp(ps_global->keyboard_charmap, "us-ascii")) + || (ps_global->display_charmap && ps_global->display_charmap[0] && + strucmp(ps_global->display_charmap, "us-ascii"))) + bit_strip_off(); + +#ifdef DEBUG + if(debug < 9) /* only on if full debugging set */ +#endif + quit_char_off(); + ps_global->low_speed = ttisslow(); + crlf_proc(0); + xonxoff_proc(F_ON(F_PRESERVE_START_STOP, ps_global)); + } + + return(result); +} + + +#if defined(SIGWINCH) && defined(TIOCGWINSZ) +jmp_buf winch_state; +int winch_occured = 0; +int ready_for_winch = 0; +#endif + +/*---------------------------------------------------------------------- + This checks whether or not a character (UNIX) + is ready to be read, or it times out. + + Args: time_out -- number of seconds before it will timeout + + Result: Returns a NO_OP_IDLE or a NO_OP_COMMAND if the timeout expires + before input is available, or a KEY_RESIZE if a resize event + occurs, or READY_TO_READ if input is available before the timeout. + ----*/ +UCS +check_for_timeout(int time_out) +{ + UCS res = NO_OP_COMMAND; + + fflush(stdout); + +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + if(!winch_occured){ + if(setjmp(winch_state) != 0){ + winch_occured = 1; + ready_for_winch = 0; + + /* + * Need to unblock signal after longjmp from handler, because + * signal is normally unblocked upon routine exit from the handler. + */ + our_sigunblock(SIGWINCH); + } + else + ready_for_winch = 1; + } + + if(winch_occured){ + winch_occured = ready_for_winch = 0; + fix_windsize(ps_global); + return(KEY_RESIZE); + } +#endif /* SIGWINCH */ + + switch(res = input_ready(time_out)){ + case BAIL_OUT: + read_bail(); /* non-tragic exit */ + /* NO RETURN */ + + case PANIC_NOW: + panic1("Select error: %s\n", error_description(errno)); + /* NO RETURN */ + + case READ_INTR: + res = NO_OP_COMMAND; + /* fall through */ + + case NO_OP_IDLE: + case NO_OP_COMMAND: + case READY_TO_READ: +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + ready_for_winch = 0; +#endif + return(res); + } + + /* not reachable */ + return(res); +} + + + +/*---------------------------------------------------------------------- + Read input characters with lots of processing for arrow keys and such (UNIX) + + Args: time_out -- The timeout to for the reads + + Result: returns the character read. Possible special chars. + + This deals with function and arrow keys as well. + + The idea is that this routine handles all escape codes so it done in + only one place. Especially so the back arrow key can work when entering + things on a line. Also so all function keys can be disabled and not + cause weird things to happen. + ---*/ +UCS +read_char(int time_out) +{ + UCS status, cc, ch; + int (*key_rec)(int); + + key_rec = key_recorder; + if(ps_global->conceal_sensitive_debugging) + key_rec = NULL; + + /* Get input from initial-keystrokes */ + if(process_config_input(&cc)){ + ch = cc; + return(ch); + } + + if((ch = check_for_timeout(time_out)) != READY_TO_READ) + goto done; + + switch(status = kbseq(pine_simple_ttgetc, key_rec, read_bail, + ps_global->input_cs, &ch)){ + case KEY_DOUBLE_ESC: + /* + * Special hack to get around comm devices eating control characters. + */ + if(check_for_timeout(5) != READY_TO_READ){ + dprint((9, "Read char: incomplete double escape timed out...\n")); + ch = KEY_JUNK; /* user typed ESC ESC, then stopped */ + goto done; + } + else + ch = READ_A_CHAR(); + + ch &= 0x7f; + + /* We allow a 3-digit number between 001 and 255 */ + if(isdigit((unsigned char) ch)){ + int n = 0, i = ch - '0'; + + if(i < 0 || i > 2){ + dprint((9, "Read char: double escape followed by 1st digit not 0, 1, or 2... (%d)\n", i)); + ch = KEY_JUNK; + goto done; /* bogus literal char value */ + } + + while(n++ < 2){ + if(check_for_timeout(5) != READY_TO_READ + || (!isdigit((unsigned char) (ch = READ_A_CHAR())) + || (n == 1 && i == 2 && ch > '5') + || (n == 2 && i == 25 && ch > '5'))){ + dprint((9, "Read char: bad double escape, either timed out or too large 3-digit num...\n")); + ch = KEY_JUNK; /* user typed ESC ESC #, stopped */ + goto done; + } + + i = (i * 10) + (ch - '0'); + } + + ch = i; + } + else{ /* or, normal case, ESC ESC c means ^c */ + if(islower((unsigned char) ch)) /* canonicalize if alpha */ + ch = toupper((unsigned char) ch); + + ch = (isalpha((unsigned char)ch) || ch == '@' + || (ch >= '[' && ch <= '_')) + ? ctrl(ch) : ((ch == SPACE) ? ctrl('@'): ch); + dprint((9, "Read char: this is a successful double escape...\n")); + } + + goto done; + +#ifdef MOUSE + case KEY_XTERM_MOUSE: + if(mouseexist()){ + /* + * Special hack to get mouse events from an xterm. + * Get the details, then pass it past the keymenu event + * handler, and then to the installed handler if there + * is one... + */ + static int down = 0; + int x, y, button; + unsigned long cmd; + + clear_cursor_pos(); + button = READ_A_CHAR() & 0x03; + + x = READ_A_CHAR() - '!'; + y = READ_A_CHAR() - '!'; + + ch = NO_OP_COMMAND; + if(button == 0){ /* xterm button 1 down */ + down = 1; + if(checkmouse(&cmd, 1, x, y)) + ch = cmd; + } + else if (down && button == 3){ + down = 0; + if(checkmouse(&cmd, 0, x, y)) + ch = cmd; + } + + goto done; + } + + break; +#endif /* MOUSE */ + + case KEY_UP : + case KEY_DOWN : + case KEY_RIGHT : + case KEY_LEFT : + case KEY_PGUP : + case KEY_PGDN : + case KEY_HOME : + case KEY_END : + case KEY_DEL : + case PF1 : + case PF2 : + case PF3 : + case PF4 : + case PF5 : + case PF6 : + case PF7 : + case PF8 : + case PF9 : + case PF10 : + case PF11 : + case PF12 : + dprint((9, "Read char returning: 0x%x %s\n", status, pretty_command(status))); + return(status); + + case CTRL_KEY_UP : + status = KEY_UP; + dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_UP)\n", status, pretty_command(status))); + return(status); + + case CTRL_KEY_DOWN : + status = KEY_DOWN; + dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_DOWN)\n", status, pretty_command(status))); + return(status); + + case CTRL_KEY_RIGHT : + status = KEY_RIGHT; + dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_RIGHT)\n", status, pretty_command(status))); + return(status); + + case CTRL_KEY_LEFT : + status = KEY_LEFT; + dprint((9, "Read char returning: 0x%x %s (for CTRL_KEY_LEFT)\n", status, pretty_command(status))); + return(status); + + case KEY_SWALLOW_Z: + status = KEY_JUNK; + case KEY_SWAL_UP: + case KEY_SWAL_DOWN: + case KEY_SWAL_LEFT: + case KEY_SWAL_RIGHT: + do + if(check_for_timeout(2) != READY_TO_READ){ + status = KEY_JUNK; + break; + } + while(!strchr("~qz", READ_A_CHAR())); + ch = (status == KEY_JUNK) ? status : status - (KEY_SWAL_UP - KEY_UP); + goto done; + + case KEY_KERMIT: + do{ + cc = ch; + if(check_for_timeout(2) != READY_TO_READ){ + status = KEY_JUNK; + break; + } + else + ch = READ_A_CHAR(); + }while(cc != '\033' && ch != '\\'); + + ch = KEY_JUNK; + goto done; + + case BADESC: + ch = KEY_JUNK; + goto done; + + case 0: /* regular character */ + default: + /* + * we used to strip (ch &= 0x7f;), but this seems much cleaner + * in the face of line noise and has the benefit of making it + * tougher to emit mistakenly labeled MIME... + */ + if((ch & ~0x7f) + && ((!ps_global->keyboard_charmap || !strucmp(ps_global->keyboard_charmap, "US-ASCII")) + && (!ps_global->display_charmap || !strucmp(ps_global->display_charmap, "US-ASCII")))){ + dprint((9, "Read char sees ch = 0x%x status=0x%x, returns KEY_JUNK\n", ch, status)); + return(KEY_JUNK); + } + else if(ch == ctrl('Z')){ + dprint((9, "Read char got ^Z, calling do_suspend\n")); + ch = do_suspend(); + dprint((9, "After do_suspend Read char returns 0x%x %s\n", ch, pretty_command(ch))); + return(ch); + } +#ifdef MOUSE + else if(ch == ctrl('\\')){ + int e; + + dprint((9, "Read char got ^\\, toggle xterm mouse\n")); + if(F_ON(F_ENABLE_MOUSE, ps_global)){ + (e=mouseexist()) ? end_mouse() : (void) init_mouse(); + if(e != mouseexist()) + q_status_message1(SM_ASYNC, 0, 2, "Xterm mouse tracking %s!", + mouseexist() ? "on" : "off"); + else if(!e) + 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)); + } + else + q_status_message1(SM_ASYNC, 0, 2, "Feature \"%s\" not enabled", + pretty_feature_name(feature_list_name(F_ENABLE_MOUSE), -1)); + + return(NO_OP_COMMAND); + } +#endif /* MOUSE */ + + + done: +#ifdef DEBUG + if(ps_global->conceal_sensitive_debugging && debug < 10){ + dprint((9, "Read char returning: <hidden char>\n")); + } + else{ + dprint((9, "Read char returning: 0x%x %s\n", ch, pretty_command(ch))); + } +#endif + + return(ch); + } + + /* not reachable */ + return(KEY_JUNK); +} + + +/*---------------------------------------------------------------------- + Reading input somehow failed and we need to shutdown now + + Args: none + + Result: pine exits + + ---*/ +void +read_bail(void) +{ + dprint((1, "read_bail: cleaning up\n")); + end_signals(1); + + /* + * This gets rid of temporary cache files for remote addrbooks. + */ + completely_done_with_adrbks(); + + /* + * This flushes out deferred changes and gets rid of temporary cache + * files for remote config files. + */ + if(ps_global->prc){ + if(ps_global->prc->outstanding_pinerc_changes) + write_pinerc(ps_global, Main, WRP_NOUSER); + + if(ps_global->prc->rd) + rd_close_remdata(&ps_global->prc->rd); + + free_pinerc_s(&ps_global->prc); + } + + /* as does this */ + if(ps_global->post_prc){ + if(ps_global->post_prc->outstanding_pinerc_changes) + write_pinerc(ps_global, Post, WRP_NOUSER); + + if(ps_global->post_prc->rd) + rd_close_remdata(&ps_global->post_prc->rd); + + free_pinerc_s(&ps_global->post_prc); + } + + sp_end(); + + dprint((1, "done with read_bail clean up\n")); + + imap_flush_passwd_cache(TRUE); + end_keyboard(F_ON(F_USE_FK,ps_global)); + end_tty_driver(ps_global); + if(filter_data_file(0)) + our_unlink(filter_data_file(0)); + + exit(0); +} + + +int +pine_simple_ttgetc(int (*fi)(int), void (*fv)(void)) +{ + int ch; + +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + if(!winch_occured){ + if(setjmp(winch_state) != 0){ + winch_occured = 1; + ready_for_winch = 0; + + /* + * Need to unblock signal after longjmp from handler, because + * signal is normally unblocked upon routine exit from the handler. + */ + our_sigunblock(SIGWINCH); + } + else + ready_for_winch = 1; + } + + if(winch_occured){ + winch_occured = ready_for_winch = 0; + fix_windsize(ps_global); + return(KEY_RESIZE); + } +#endif /* SIGWINCH */ + + ch = simple_ttgetc(fi, fv); + +#if defined(SIGWINCH) && defined(TIOCGWINSZ) + ready_for_winch = 0; +#endif + + return(ch); +} + + + +extern char term_name[]; +/* ------------------------------------------------------------------- + Set up the keyboard -- usually enable some function keys (UNIX) + + Args: struct pine + +So far all we do here is turn on keypad mode for certain terminals + +Hack for NCSA telnet on an IBM PC to put the keypad in the right mode. +This is the same for a vtXXX terminal or [zh][12]9's which we have +a lot of at UW + ----*/ +void +init_keyboard(int use_fkeys) +{ + if(use_fkeys && (!strucmp(term_name,"vt102") + || !strucmp(term_name,"vt100"))) + printf("\033\133\071\071\150"); +} + + + +/*---------------------------------------------------------------------- + Clear keyboard, usually disable some function keys (UNIX) + + Args: pine state (terminal type) + + Result: keyboard state reset + ----*/ +void +end_keyboard(int use_fkeys) +{ + if(use_fkeys && (!strcmp(term_name, "vt102") + || !strcmp(term_name, "vt100"))){ + printf("\033\133\071\071\154"); + fflush(stdout); + } +} + + +/* + * This is a bare-bones implementation which is missing most of the + * features of the real optionally_enter. The initial value of string is + * isgnored. The escape_list is ignored. The help is not implemented. The + * only flag implemented is OE_PASSWD. We don't go into raw mode so the + * only input possible is a line (the EOL is stripped before returning). + */ +int +pre_screen_config_opt_enter(char *string, int string_size, char *prompt, + ESCKEY_S *escape_list, HelpType help, int *flags) +{ + char *pw; + int return_v = -10; + + while(return_v == -10) { + + if(flags && (*flags & (OE_PASSWD | OE_PASSWD_NOAST))){ + if((pw = getpass(prompt)) != NULL){ + if(strlen(pw) < string_size){ + strncpy(string, pw, string_size); + string[string_size-1] = '\0'; + return_v = 0; + } + else{ + fputs("Password too long\n", stderr); + return_v = -1; + } + } + else + return_v = 1; /* cancel? */ + } + else{ + char *p; + + fputs(prompt, stdout); + fgets(string, string_size, stdin); + string[string_size-1] = '\0'; + if((p = strpbrk(string, "\r\n")) != NULL) + *p = '\0'; + + return_v = 0; + } + } + + return(return_v); +} + + |