diff options
Diffstat (limited to 'chinese/pine4/files/patch-ar')
-rw-r--r-- | chinese/pine4/files/patch-ar | 6672 |
1 files changed, 5414 insertions, 1258 deletions
diff --git a/chinese/pine4/files/patch-ar b/chinese/pine4/files/patch-ar index d70d02915af1..60458238b332 100644 --- a/chinese/pine4/files/patch-ar +++ b/chinese/pine4/files/patch-ar @@ -1,1260 +1,5416 @@ ---- pine/folder.c.orig Tue Jul 7 07:13:25 1998 -+++ pine/folder.c Wed Jul 15 17:02:32 1998 -@@ -63,13 +63,13 @@ - ((X)->dir->status&CNTXT_PARTFIND) == 0) - #define FLDR_NAME(X) ((X) ? ((X)->nickname ? (X)->nickname : (X)->name) :"") - #define SUBSCRIBE_PMT \ -- "Enter newsgroup name (or partial name to get a list): " --#define LISTMODE_GRIPE "Use \"X\" to mark selections in list mode" --#define SEL_ALTER_PMT "ALTER folder selection : " --#define SEL_TEXT_PMT "Select by folder Name or Contents ? " --#define SEL_PROP_PMT "Select by which folder property ? " -+ "輸入新聞組群名稱(或部份名稱以取得列表):" -+#define LISTMODE_GRIPE "以 \"X\" 來在列表模式中標示選擇" -+#define SEL_ALTER_PMT "更改資料匣的選擇:" -+#define SEL_TEXT_PMT "根據資料匣名稱或內容選擇?" -+#define SEL_PROP_PMT "根據哪一個資料匣性質?" - #define DIR_FOLD_PMT \ -- "Folder by the same name *MAY* get deleted as well. Continue" -+ "同名稱的資料匣 *可能* 被刪除。繼續" - - #define mail_list(S, R, N) mail_list_internal(S, R, N) - -@@ -270,18 +270,18 @@ - /* - * Various screen keymenu/command binding s. - */ --#define PREVC_MENU {"P", "PrevCltn", {MC_PREVITEM, 1, {'p'}}, KS_NONE} --#define NEXTC_MENU {"N", "NextCltn", {MC_NEXTITEM, 2, {'n',TAB}}, KS_NONE} --#define DELC_MENU {"D", "Del Cltn", {MC_DELETE,2,{'d',KEY_DEL}}, KS_NONE} --#define PREVF_MENU {"P", "PrevFldr", {MC_PREVITEM, 1, {'p'}}, KS_NONE} --#define NEXTF_MENU {"N", "NextFldr", {MC_NEXTITEM, 2, {'n',TAB}}, KS_NONE} --#define CIND_MENU {"I", "CurIndex", {MC_INDEX,1,{'i'}}, KS_FLDRINDEX} -+#define PREVC_MENU {"P", "前一總集", {MC_PREVITEM, 1, {'p'}}, KS_NONE} -+#define NEXTC_MENU {"N", "次一總集", {MC_NEXTITEM, 2, {'n',TAB}}, KS_NONE} -+#define DELC_MENU {"D", "刪除總集", {MC_DELETE,2,{'d',KEY_DEL}}, KS_NONE} -+#define PREVF_MENU {"P", "前一資料匣", {MC_PREVITEM, 1, {'p'}}, KS_NONE} -+#define NEXTF_MENU {"N", "次一資料匣", {MC_NEXTITEM, 2, {'n',TAB}}, KS_NONE} -+#define CIND_MENU {"I", "索引", {MC_INDEX,1,{'i'}}, KS_FLDRINDEX} - - static struct key context_mgr_keys[] = - {HELP_MENU, - OTHER_MENU, -- {"<", "Main Menu", {MC_MAIN,3,{'m','<',','}}, KS_EXITMODE}, -- {">", "[View Cltn]", -+ {"<", "主選單", {MC_MAIN,3,{'m','<',','}}, KS_EXITMODE}, -+ {">", "[檢視總集]", - {MC_CHOICE,5,{'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVC_MENU, - NEXTC_MENU, -@@ -313,15 +313,15 @@ - static struct key context_cfg_keys[] = - {HELP_MENU, - OTHER_MENU, -- {"E", "Exit Setup", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -- {"C", "[Change]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE}, -+ {"E", "離開設定", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"C", "[修改]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVC_MENU, - NEXTC_MENU, - PREVPAGE_MENU, - NEXTPAGE_MENU, -- {"A", "Add Cltn", {MC_ADD,1,{'a'}}, KS_NONE}, -+ {"A", "新增總集", {MC_ADD,1,{'a'}}, KS_NONE}, - DELC_MENU, -- {"$", "Shuffle", {MC_SHUFFLE,1,{'$'}},KS_NONE}, -+ {"$", "重整", {MC_SHUFFLE,1,{'$'}},KS_NONE}, - WHEREIS_MENU, - - HELP_MENU, -@@ -340,9 +340,9 @@ - - static struct key context_select_keys[] = - {HELP_MENU, -- {"E", "ExitSelect", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, - NULL_MENU, -- {">", "[View Cltn]", -+ {">", "[檢視總集]", - {MC_CHOICE, 5, {'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVC_MENU, - NEXTC_MENU, -@@ -356,9 +356,9 @@ - - static struct key context_fcc_keys[] = - {HELP_MENU, -- {"E", "ExitSelect", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, - NULL_MENU, -- {">", "[View Cltn]", -+ {">", "[檢視總集]", - {MC_CHOICE, 5, {'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVC_MENU, - NEXTC_MENU, -@@ -373,16 +373,16 @@ - static struct key folder_keys[] = - {HELP_MENU, - OTHER_MENU, -- {"<", NULL, {MC_EXIT,3,{' ','<',','}}, KS_NONE}, -- {">", "[View Fldr]", -+ {"M", NULL, {MC_EXIT,3,{' ','<',','}}, KS_NONE}, -+ {">", "[檢視檔案匣]", - {MC_CHOICE,5,{'v','>','.',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVF_MENU, - NEXTF_MENU, - PREVPAGE_MENU, - NEXTPAGE_MENU, -- {"A","Add",{MC_ADDFLDR,1,{'a'}},KS_NONE}, -+ {"A","新增",{MC_ADDFLDR,1,{'a'}},KS_NONE}, - DELETE_MENU, -- {"R","Rename",{MC_RENAMEFLDR,1,{'r'}}, KS_NONE}, -+ {"R","更名",{MC_RENAMEFLDR,1,{'r'}}, KS_NONE}, - WHEREIS_MENU, - - HELP_MENU, -@@ -394,9 +394,9 @@ - CIND_MENU, - COMPOSE_MENU, - PRYNTTXT_MENU, -- {"Z", "ZoomMode", {MC_ZOOM,1,{'z'}}, KS_NONE}, -- {";","Select",{MC_SELECT,1,{';'}},KS_SELECT}, -- {":","SelectCur",{MC_SELCUR,1,{':'}},KS_SELECT}}; -+ {"Z", "縮放模式", {MC_ZOOM,1,{'z'}}, KS_NONE}, -+ {";","選擇",{MC_SELECT,1,{';'}},KS_SELECT}, -+ {":","選擇目前的",{MC_SELCUR,1,{':'}},KS_SELECT}}; - INST_KEY_MENU(folder_km, folder_keys); - #define KM_COL_KEY 2 - #define KM_SEL_KEY 3 -@@ -408,9 +408,9 @@ - - static struct key folder_sel_keys[] = - {HELP_MENU, -- {"E", "ExitSelect", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -- {"<", "Collections", {MC_COLLECTIONS,2,{'<',','}}, KS_NONE}, -- {"S", "[Select]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}},KS_NONE}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"<", "總集", {MC_COLLECTIONS,2,{'<',','}}, KS_NONE}, -+ {"S", "[選擇]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}},KS_NONE}, - PREVF_MENU, - NEXTF_MENU, - PREVPAGE_MENU, -@@ -424,9 +424,9 @@ - - static struct key folder_sub_sel_keys[] = - {HELP_MENU, -- {"E", "ExitSelect", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, - NULL_MENU, -- {"S", "[Select]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}},KS_NONE}, -+ {"S", "[選擇]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}},KS_NONE}, - PREVF_MENU, - NEXTF_MENU, - PREVPAGE_MENU, -@@ -440,9 +440,9 @@ - - static struct key folder_fcc_keys[] = - {HELP_MENU, -- {"E", "ExitSelect", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -- {"<", "Collections", {MC_COLLECTIONS,2,{'<',','}}, KS_NONE}, -- {"S", "[Select]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"<", "總集", {MC_COLLECTIONS,2,{'<',','}}, KS_NONE}, -+ {"S", "[選擇]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, - KS_NONE}, - PREVF_MENU, - NEXTF_MENU, -@@ -458,9 +458,9 @@ - - static struct key folder_sub_keys[] = - {HELP_MENU, -- {"S", "Subscribe", {MC_CHOICE,1,{'s'}}, KS_NONE}, -- {"E", "ExitSubscb", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -- {NULL, "[Select]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE}, -+ {"S", "訂閱\", {MC_CHOICE,1,{'s'}}, KS_NONE}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {NULL, "[選擇]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVF_MENU, - NEXTF_MENU, - PREVPAGE_MENU, -@@ -477,8 +477,8 @@ - static struct key folder_post_keys[] = - {HELP_MENU, - NULL_MENU, -- {"E", "ExitSelect", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -- {"S", "[Select]", {MC_CHOICE, 3, {'s',ctrl('M'),ctrl('J')}}, KS_NONE}, -+ {"E", "離開", {MC_EXIT,1,{'e'}}, KS_EXITMODE}, -+ {"S", "[選擇]", {MC_CHOICE, 3, {'s',ctrl('M'),ctrl('J')}}, KS_NONE}, - PREVF_MENU, - NEXTF_MENU, - PREVPAGE_MENU, -@@ -573,12 +573,12 @@ - mailcap_free(); /* free resources we won't be using for a while */ - - memset(&css, 0, sizeof(CONT_SCR_S)); -- css.title = "SETUP COLLECTION LIST"; -+ css.title = "設定總集列表"; - css.print_string = "contexts "; - css.start = ps->context_current; - css.contexts = &ps_global->context_list; - css.help.text = h_collection_maint; -- css.help.title = "HELP FOR SETUP COLLECTION"; -+ css.help.title = "設定總集的輔助說明"; - css.keymenu = &c_cfg_km; - css.edit = 1; - -@@ -867,7 +867,7 @@ - /* leave (*new_dir)->ref == NULL */ - } - -- sprintf(tmp_20k_buf, "List of folders matching \"%s*\"", folder); -+ sprintf(tmp_20k_buf, "符合 \"%s*\" 的資料匣列表", folder); - (*new_dir)->desc = cpystr(tmp_20k_buf); - } - -@@ -966,12 +966,12 @@ - CONT_SCR_S css; - - memset(&css, 0, sizeof(CONT_SCR_S)); -- css.title = "COLLECTION LIST"; -+ css.title = "總集列表"; - css.print_string = "contexts "; - css.start = start; - css.contexts = &ps_global->context_list; - css.help.text = h_collection_screen; -- css.help.title = "HELP FOR COLLECTION LIST"; -+ css.help.title = "總集列表的輔助說明"; - css.keymenu = km; - css.edit = edit_config; - -@@ -1042,7 +1042,7 @@ - pbuf.browse_help = h_composer_browse; - pbuf.attach_help = h_composer_ctrl_j; - pbuf.composer_help = h_composer; -- sprintf(tmp, "FOLDER COLLECTION %s", func); -+ sprintf(tmp, "資料匣總集 %s", func); - pbuf.pine_anchor = set_titlebar(tmp, ps_global->mail_stream, - ps_global->context_current, - ps_global->cur_folder,ps_global->msgmap, -@@ -1278,10 +1278,10 @@ - else - exists = (i & FEX_ISDIR); - -- sprintf(prompt, "Exit%s" , -+ sprintf(prompt, "離開%s" , - exists -- ? " and save changes" -- : ", saving changes and creating Path"); -+ ? " 並存檔" -+ : ",存檔並建立路徑"); - if(want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){ - if(!exists && !mail_create(NULL, tmp)){ - flush_status_messages(1); /* mail_create gripes */ -@@ -1313,7 +1313,7 @@ - char *rstr = NULL; - void (*redraw)() = ps_global->redrawer; - #define CCA_PROMPT \ -- "Cancel Add (answering \"Yes\" will abandon any changes made) " -+ "取消新增 (回答 \"Yes\" 將放棄先前做過的任何改變) " - - ps_global->redrawer = redraw_pico; - fix_windsize(ps_global); -@@ -1430,7 +1430,7 @@ - /*BUG: test writing with NNTP to misc.test via mark's code. reasonable err msg?*/ - if(NEWS_TEST(fs->context)) { - q_status_message(SM_ORDER | SM_DING, 3, 4, -- "Can't save messages to bulletin boards or news groups!"); -+ "無法將訊息存至電子佈告欄或新聞組群上!"); - return(0); - } - #endif -@@ -1445,7 +1445,7 @@ - FSTATE_S *fs; - { - if(!strncmp(f->prefix, "SUB", 3)){ -- q_status_message1(SM_ORDER, 0, 4, "Already subscribed to \"%s\"", -+ q_status_message1(SM_ORDER, 0, 4, "已訂閱\至 \"%s\"", - FLDR_NAME(f)); - return(0); - } -@@ -1458,7 +1458,7 @@ - fl_hdr_gen(ps) - struct pine *ps; - { -- set_titlebar("FOLDER LIST", ps->mail_stream, ps->context_current, -+ set_titlebar("信件匣列表", ps->mail_stream, ps->context_current, - ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0); - } - -@@ -1573,7 +1573,7 @@ - { - int ch, cmd, mangled_footer, mangled_header, - n, rc, cur_row, cur_col, km_size, was_dir = -1, -- km_popped = 0, listmode = 0, done = 0; -+ km_popped = 0, listmode = 0, done = 0, exit_to_main = 0; - unsigned short new_col; - FOLDER_S *cur_f = NULL; - STRINGLIST *sl = NULL; -@@ -1676,7 +1676,7 @@ - } - else{ - clrbitn(KM_MAIN_KEY, bitmap); -- km.keys[KM_COL_KEY].label = "Main Menu"; -+ km.keys[KM_COL_KEY].label = "主選單"; - km.keys[KM_COL_KEY].bind.cmd = MC_MAIN; - km.keys[KM_COL_KEY].bind.ch[0] = 'm'; - } -@@ -1696,14 +1696,14 @@ - if(listmode){ - clrbitn(SB_LIST_KEY, bitmap); - km.keys[SB_SEL_KEY].name = "X"; -- km.keys[SB_SEL_KEY].label = "[Set/Unset]"; -+ km.keys[SB_SEL_KEY].label = "[設定/取消設定]"; - km.keys[SB_SEL_KEY].bind.cmd = MC_SELCUR; - km.keys[SB_SEL_KEY].bind.ch[0] = 'x'; - } - else{ - clrbitn(SB_SUB_KEY, bitmap); - km.keys[SB_SEL_KEY].name = "S"; -- km.keys[SB_SEL_KEY].label = "[Subscribe]"; -+ km.keys[SB_SEL_KEY].label = "[訂閱\]"; - km.keys[SB_SEL_KEY].bind.cmd = MC_CHOICE; - km.keys[SB_SEL_KEY].bind.ch[0] = 's'; - } -@@ -1717,7 +1717,7 @@ - } - - if(cur_f && cur_f->isdir){ -- static struct key sel_key = {">", "[View Dir]", -+ static struct key sel_key = {">", "[檢視目錄]", - {MC_CHOICE,5, - {'s','>','.', - ctrl('M'),ctrl('J')}}, -@@ -1843,8 +1843,15 @@ - /*---------------------- Key left --------------*/ - case MC_CHARLEFT : - case MC_PREVITEM : -+ if (exit_to_main) -+ { -+ ps_global->next_screen = main_menu_screen; -+ done++; +--- pine/osdep/os-bsf.c.orig Tue Jul 28 08:35:04 1998 ++++ pine/osdep/os-bsf.c Tue Jul 28 08:35:05 1998 +@@ -0,0 +1,5413 @@ ++/*---------------------------------------------------------------------- ++ ++ T H E P I N E M A I L S Y S T E M ++ ++ Laurence Lundblade and Mike Seibel ++ Networks and Distributed Computing ++ Computing and Communications ++ University of Washington ++ Administration Builiding, AG-44 ++ Seattle, Washington, 98195, USA ++ Internet: lgl@CAC.Washington.EDU ++ mikes@CAC.Washington.EDU ++ ++ Please address all bugs and comments to "pine-bugs@cac.washington.edu" ++ ++ ++ Pine and Pico are registered trademarks of the University of Washington. ++ No commercial use of these trademarks may be made without prior written ++ permission of the University of Washington. ++ ++ Pine, Pico, and Pilot software and its included text are Copyright ++ 1989-1998 by the University of Washington. ++ ++ The full text of our legal notices is contained in the file called ++ CPYRIGHT, included with this distribution. ++ ++ ++ Pine is in part based on The Elm Mail System: ++ *********************************************************************** ++ * The Elm Mail System - Revision: 2.13 * ++ * * ++ * Copyright (c) 1986, 1987 Dave Taylor * ++ * Copyright (c) 1988, 1989 USENET Community Trust * ++ *********************************************************************** ++ ++ ++ ----------------------------------------------------------------------*/ ++ ++/*====================================================================== ++ ++ This contains most of Pine's interface to the local operating system ++and hardware. Hopefully this file, os-xxx.h and makefile.xxx are the ++only ones that have to be modified for most ports. Signals.c, ttyin.c, ++and ttyout.c also have some dependencies. See the doc/tech-notes for ++notes on porting Pine to other platforms. Here is a list of the functions ++required for an implementation: ++ ++ ++ File System Access ++ can_access -- See if a file can be accessed ++ name_file_size -- Return the number of bytes in the file (by name) ++ fp_file_size -- Return the number of bytes in the file (by FILE *) ++ name_file_mtime -- Return the mtime of a file (by name) ++ fp_file_mtime -- Return the mtime of a file (by FILE *) ++ file_attrib_copy -- Copy attributes of one file to another. ++ is_writable_dir -- Check to see if directory exists and is writable ++ create_mail_dir -- Make a directory ++ rename_file -- change name of a file ++ build_path -- Put together a file system path ++ last_cmpnt -- Returns pointer to last component of path ++ expand_foldername -- Expand a folder name to full path ++ fnexpand -- Do filename exansion for csh style "~" ++ filter_filename -- Make sure file name hasn't got weird chars ++ cntxt_allowed -- Check whether a pathname is allowed for read/write ++ disk_quota -- Check the user's disk quota ++ read_file -- Read whole file into memory (for small files) ++ create_tmpfile -- Just like ANSI C tmpfile function ++ temp_nam -- Almost like common tempnam function ++ fget_pos,fset_pos -- Just like ANSI C fgetpos, fsetpos functions ++ ++ Abort ++ coredump -- Abort running Pine dumping core if possible ++ ++ System Name and Domain ++ hostname -- Figure out the system's host name, only ++ used internally in this file. ++ getdomainnames -- Figure out the system's domain name ++ canonical_name -- Returns canonical form of host name ++ ++ Job Control ++ have_job_control -- Returns 1 if job control exists ++ stop_process -- What to do to stop process when it's time to stop ++ (only used if have_job_control returns 1) ++ ++ System Error Messages (in case given one is a problem) ++ error_description -- Returns string describing error ++ ++ System Password and Accounts ++ gcos_name -- Parses full name from system, only used ++ locally in this file so if you don't use it you ++ don't need it ++ get_user_info -- Finds in login name, full name, and homedir ++ local_name_lookup -- Get full name of user on system ++ change_passwd -- Calls system password changer ++ ++ MIME utilities ++ mime_can_display -- Can we display this type/subtype? ++ exec_mailcap_cmd -- Run the mailcap command to view a type/subtype. ++ exec_mailcap_test_cmd -- Run mailcap test= test command. ++ ++ Other stuff ++ srandom -- Dummy srandom if you don't have this function ++ init_debug ++ do_debug ++ save_debug_on_crash ++ ++ ====*/ ++ ++ ++#include "headers.h" ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Check if we can access a file in a given way ++ ++ Args: file -- The file to check ++ mode -- The mode ala the access() system call, see ACCESS_EXISTS ++ and friends in pine.h. ++ ++ Result: returns 0 if the user can access the file according to the mode, ++ -1 if he can't (and errno is set). ++ ----*/ ++int ++can_access(file, mode) ++ char *file; ++ int mode; ++{ ++ return(access(file, mode)); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Check if we can access a file in a given way in the given path ++ ++ Args: path -- The path to look for "file" in ++ file -- The file to check ++ mode -- The mode ala the access() system call, see ACCESS_EXISTS ++ and friends in pine.h. ++ ++ Result: returns 0 if the user can access the file according to the mode, ++ -1 if he can't (and errno is set). ++ ----*/ ++can_access_in_path(path, file, mode) ++ char *path, *file; ++ int mode; ++{ ++ char tmp[MAXPATH], *path_copy, *p, *t; ++ int rv = -1; ++ ++ if(!path || !*path || *file == '/'){ ++ rv = access(file, mode); ++ } ++ else if(*file == '~'){ ++ strcpy(tmp, file); ++ rv = fnexpand(tmp, sizeof(tmp)) ? access(tmp, mode) : -1; ++ } ++ else{ ++ for(p = path_copy = cpystr(path); p && *p; p = t){ ++ if(t = strindex(p, ':')) ++ *t++ = '\0'; ++ ++ sprintf(tmp, "%s/%s", p, file); ++ if((rv = access(tmp, mode)) == 0) ++ break; ++ } ++ ++ fs_give((void **)&path_copy); ++ } ++ ++ return(rv); ++} ++ ++/*---------------------------------------------------------------------- ++ Return the number of bytes in given file ++ ++ Args: file -- file name ++ ++ Result: the number of bytes in the file is returned or ++ -1 on error, in which case errno is valid ++ ----*/ ++long ++name_file_size(file) ++ char *file; ++{ ++ struct stat buffer; ++ ++ if(stat(file, &buffer) != 0) ++ return(-1L); ++ ++ return((long)buffer.st_size); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Return the number of bytes in given file ++ ++ Args: fp -- FILE * for open file ++ ++ Result: the number of bytes in the file is returned or ++ -1 on error, in which case errno is valid ++ ----*/ ++long ++fp_file_size(fp) ++ FILE *fp; ++{ ++ struct stat buffer; ++ ++ if(fstat(fileno(fp), &buffer) != 0) ++ return(-1L); ++ ++ return((long)buffer.st_size); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Return the modification time of given file ++ ++ Args: file -- file name ++ ++ Result: the time of last modification (mtime) of the file is returned or ++ -1 on error, in which case errno is valid ++ ----*/ ++time_t ++name_file_mtime(file) ++ char *file; ++{ ++ struct stat buffer; ++ ++ if(stat(file, &buffer) != 0) ++ return((time_t)(-1)); ++ ++ return(buffer.st_mtime); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Return the modification time of given file ++ ++ Args: fp -- FILE * for open file ++ ++ Result: the time of last modification (mtime) of the file is returned or ++ -1 on error, in which case errno is valid ++ ----*/ ++time_t ++fp_file_mtime(fp) ++ FILE *fp; ++{ ++ struct stat buffer; ++ ++ if(fstat(fileno(fp), &buffer) != 0) ++ return((time_t)(-1)); ++ ++ return(buffer.st_mtime); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Copy the mode, owner, and group of sourcefile to targetfile. ++ ++ Args: targetfile -- ++ sourcefile -- ++ ++ We don't bother keeping track of success or failure because we don't care. ++ ----*/ ++void ++file_attrib_copy(targetfile, sourcefile) ++ char *targetfile; ++ char *sourcefile; ++{ ++ struct stat buffer; ++ ++ if(stat(sourcefile, &buffer) == 0){ ++ chmod(targetfile, buffer.st_mode); ++#if !defined(DOS) && !defined(OS2) ++ chown(targetfile, buffer.st_uid, buffer.st_gid); ++#endif ++ } ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Check to see if a directory exists and is writable by us ++ ++ Args: dir -- directory name ++ ++ Result: returns 0 if it exists and is writable ++ 1 if it is a directory, but is not writable ++ 2 if it is not a directory ++ 3 it doesn't exist. ++ ----*/ ++is_writable_dir(dir) ++ char *dir; ++{ ++ struct stat sb; ++ ++ if(stat(dir, &sb) < 0) ++ /*--- It doesn't exist ---*/ ++ return(3); ++ ++ if(!(sb.st_mode & S_IFDIR)) ++ /*---- it's not a directory ---*/ ++ return(2); ++ ++ if(can_access(dir, 07)) ++ return(1); ++ else ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Create the mail subdirectory. ++ ++ Args: dir -- Name of the directory to create ++ ++ Result: Directory is created. Returns 0 on success, else -1 on error ++ and errno is valid. ++ ----*/ ++create_mail_dir(dir) ++ char *dir; ++{ ++ if(mkdir(dir, 0700) < 0) ++ return(-1); ++ ++ (void)chmod(dir, 0700); ++ /* Some systems need this, on others we don't care if it fails */ ++ (void)chown(dir, getuid(), getgid()); ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Rename a file ++ ++ Args: tmpfname -- Old name of file ++ fname -- New name of file ++ ++ Result: File is renamed. Returns 0 on success, else -1 on error ++ and errno is valid. ++ ----*/ ++rename_file(tmpfname, fname) ++ char *tmpfname, *fname; ++{ ++ return(rename(tmpfname, fname)); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Paste together two pieces of a file name path ++ ++ Args: pathbuf -- Put the result here ++ first_part -- of path name ++ second_part -- of path name ++ ++ Result: New path is in pathbuf. No check is made for overflow. Note that ++ we don't have to check for /'s at end of first_part and beginning ++ of second_part since multiple slashes are ok. ++ ++BUGS: This is a first stab at dealing with fs naming dependencies, and others ++still exist. ++ ----*/ ++void ++build_path(pathbuf, first_part, second_part) ++ char *pathbuf, *first_part, *second_part; ++{ ++ if(!first_part) ++ strcpy(pathbuf, second_part); ++ else ++ sprintf(pathbuf, "%s%s%s", first_part, ++ (*first_part && first_part[strlen(first_part)-1] != '/') ++ ? "/" : "", ++ second_part); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Test to see if the given file path is absolute ++ ++ Args: file -- file path to test ++ ++ Result: TRUE if absolute, FALSE otw ++ ++ ----*/ ++int ++is_absolute_path(path) ++ char *path; ++{ ++ return(path && (*path == '/' || *path == '~')); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Return pointer to last component of pathname. ++ ++ Args: filename -- The pathname. ++ ++ Result: Returned pointer points to last component in the input argument. ++ ----*/ ++char * ++last_cmpnt(filename) ++ char *filename; ++{ ++ register char *p = NULL, *q = filename; ++ ++ while(q = strchr(q, '/')) ++ if(*++q) ++ p = q; ++ ++ return(p); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Expand a folder name, taking account of the folders_dir and `~'. ++ ++ Args: filename -- The name of the file that is the folder ++ ++ Result: The folder name is expanded in place. ++ Returns 0 and queues status message if unsuccessful. ++ Input string is overwritten with expanded name. ++ Returns 1 if successful. ++ ++BUG should limit length to MAXPATH ++ ----*/ ++int ++expand_foldername(filename) ++ char *filename; ++{ ++ char temp_filename[MAXPATH+1]; ++ ++ dprint(5, (debugfile, "=== expand_foldername called (%s) ===\n",filename)); ++ ++ /* ++ * We used to check for valid filename chars here if "filename" ++ * didn't refer to a remote mailbox. This has been rethought ++ */ ++ ++ strcpy(temp_filename, filename); ++ if(strucmp(temp_filename, "inbox") == 0) { ++ strcpy(filename, ps_global->VAR_INBOX_PATH == NULL ? "inbox" : ++ ps_global->VAR_INBOX_PATH); ++ } else if(temp_filename[0] == '{') { ++ strcpy(filename, temp_filename); ++ } else if(ps_global->restricted ++ && (strindex("./~", temp_filename[0]) != NULL ++ || srchstr(temp_filename,"/../"))){ ++ q_status_message(SM_ORDER, 0, 3, "僅能開啟本地的檔案匣"); ++ return(0); ++ } else if(temp_filename[0] == '*') { ++ strcpy(filename, temp_filename); ++ } else if(ps_global->VAR_OPER_DIR && srchstr(temp_filename,"..")){ ++ q_status_message(SM_ORDER, 0, 3, ++ "檔案匣名稱中不允許\有 \"..\""); ++ return(0); ++ } else if (temp_filename[0] == '~'){ ++ if(fnexpand(temp_filename, sizeof(temp_filename)) == NULL) { ++ char *p = strindex(temp_filename, '/'); ++ if(p != NULL) ++ *p = '\0'; ++ q_status_message1(SM_ORDER, 3, 3, ++ "檔案匣展開錯誤:\"%s\" 未知的使用者", ++ temp_filename); ++ return(0); ++ } ++ strcpy(filename, temp_filename); ++ } else if(temp_filename[0] == '/') { ++ strcpy(filename, temp_filename); ++ } else if(F_ON(F_USE_CURRENT_DIR, ps_global)){ ++ strcpy(filename, temp_filename); ++ } else if(ps_global->VAR_OPER_DIR){ ++ build_path(filename, ps_global->VAR_OPER_DIR, temp_filename); ++ } else { ++ build_path(filename, ps_global->home_dir, temp_filename); ++ } ++ dprint(5, (debugfile, "returning \"%s\"\n", filename)); ++ return(1); ++} ++ ++ ++ ++struct passwd *getpwnam(); ++ ++/*---------------------------------------------------------------------- ++ Expand the ~ in a file ala the csh (as home directory) ++ ++ Args: buf -- The filename to expand (nothing happens unless begins with ~) ++ len -- The length of the buffer passed in (expansion is in place) ++ ++ Result: Expanded string is returned using same storage as passed in. ++ If expansion fails, NULL is returned ++ ----*/ ++char * ++fnexpand(buf, len) ++ char *buf; ++ int len; ++{ ++ struct passwd *pw; ++ register char *x,*y; ++ char name[20]; ++ ++ if(*buf == '~') { ++ for(x = buf+1, y = name; *x != '/' && *x != '\0'; *y++ = *x++); ++ *y = '\0'; ++ if(x == buf + 1) ++ pw = getpwuid(getuid()); ++ else ++ pw = getpwnam(name); ++ if(pw == NULL) ++ return((char *)NULL); ++ if(strlen(pw->pw_dir) + strlen(buf) > len) { ++ return((char *)NULL); ++ } ++ rplstr(buf, x - buf, pw->pw_dir); ++ } ++ return(len ? buf : (char *)NULL); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Filter file names for strange characters ++ ++ Args: file -- the file name to check ++ ++ Result: Returns NULL if file name is OK ++ Returns formatted error message if it is not ++ ----*/ ++char * ++filter_filename(file) ++ char *file; ++{ ++#ifdef ALLOW_WEIRD ++ static char illegal[] = {'\177', '\0'}; ++#else ++ static char illegal[] = {'"', '#', '$', '%', '&', '\'','(', ')','*', ++ ',', ':', ';', '<', '=', '>', '?', '[', ']', ++ '\\', '^', '|', '\177', '\0'}; ++#endif ++ static char error[100]; ++ char ill_file[MAXPATH+1], *ill_char, *ptr, e2[10]; ++ int i; ++ ++ for(ptr = file; *ptr == ' '; ptr++) ; /* leading spaces gone */ ++ ++ while(*ptr && (unsigned char)(*ptr) > ' ' && strindex(illegal, *ptr) == 0) ++ ptr++; ++ ++ if(*ptr != '\0') { ++ if(*ptr == ' ') { ++ ill_char = "<space>"; ++ } else if(*ptr == '\n') { ++ ill_char = "<newline>"; ++ } else if(*ptr == '\r') { ++ ill_char = "<carriage return>"; ++ } else if(*ptr == '\t') { ++ ill_char = "<tab>"; ++ } else if(*ptr < ' ') { ++ sprintf(e2, "control-%c", *ptr + '@'); ++ ill_char = e2; ++ } else if (*ptr == '\177') { ++ ill_char = "<del>"; ++ } else { ++ e2[0] = *ptr; ++ e2[1] = '\0'; ++ ill_char = e2; ++ } ++ if(ptr != file) { ++ strncpy(ill_file, file, ptr - file); ++ ill_file[ptr - file] = '\0'; ++ sprintf(error, ++ "Character \"%s\" after \"%s\" not allowed in file name", ++ ill_char, ill_file); ++ } else { ++ sprintf(error, ++ "First character, \"%s\", not allowed in file name", ++ ill_char); ++ } ++ ++ return(error); ++ } ++ ++ if((i=is_writable_dir(file)) == 0 || i == 1){ ++ sprintf(error, "\"%s\" is a directory", file); ++ return(error); ++ } ++ ++ if(ps_global->restricted || ps_global->VAR_OPER_DIR){ ++ for(ptr = file; *ptr == ' '; ptr++) ; /* leading spaces gone */ ++ ++ if((ptr[0] == '.' && ptr[1] == '.') || srchstr(ptr, "/../")){ ++ sprintf(error, "\"..\" not allowed in filename"); ++ return(error); ++ } ++ } ++ ++ return((char *)NULL); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Check to see if user is allowed to read or write this folder. ++ ++ Args: s -- the name to check ++ ++ Result: Returns 1 if OK ++ Returns 0 and posts an error message if access is denied ++ ----*/ ++int ++cntxt_allowed(s) ++ char *s; ++{ ++ struct variable *vars = ps_global->vars; ++ int retval = 1; ++ MAILSTREAM stream; /* fake stream for error message in mm_notify */ ++ ++ if(ps_global->restricted ++ && (strindex("./~", s[0]) || srchstr(s, "/../"))){ ++ stream.mailbox = s; ++ mm_notify(&stream, "Restricted mode doesn't allow operation", WARN); ++ retval = 0; ++ } ++ else if(VAR_OPER_DIR ++ && s[0] != '{' && !(s[0] == '*' && s[1] == '{') ++ && strucmp(s,ps_global->inbox_name) != 0 ++ && strcmp(s, ps_global->VAR_INBOX_PATH) != 0){ ++ char *p, *free_this = NULL; ++ ++ p = s; ++ if(strindex(s, '~')){ ++ p = strindex(s, '~'); ++ free_this = (char *)fs_get(strlen(p) + 200); ++ strcpy(free_this, p); ++ fnexpand(free_this, strlen(p)+200); ++ p = free_this; ++ } ++ else if(p[0] != '/'){ /* add home dir to relative paths */ ++ free_this = p = (char *)fs_get(strlen(s) ++ + strlen(ps_global->home_dir) + 2); ++ build_path(p, ps_global->home_dir, s); ++ } ++ ++ if(!in_dir(VAR_OPER_DIR, p)){ ++ char err[200]; ++ ++ sprintf(err, "Not allowed outside of %s", VAR_OPER_DIR); ++ stream.mailbox = p; ++ mm_notify(&stream, err, WARN); ++ retval = 0; ++ } ++ else if(srchstr(p, "/../")){ /* check for .. in path */ ++ stream.mailbox = p; ++ mm_notify(&stream, "\"..\" not allowed in name", WARN); ++ retval = 0; ++ } ++ ++ if(free_this) ++ fs_give((void **)&free_this); ++ } ++ ++ return retval; ++} ++ ++ ++ ++#if defined(USE_QUOTAS) ++ ++/*---------------------------------------------------------------------- ++ This system doesn't have disk quotas. ++ Return space left in disk quota on file system which given path is in. ++ ++ Args: path - Path name of file or directory on file system of concern ++ over - pointer to flag that is set if the user is over quota ++ ++ Returns: If *over = 0, the number of bytes free in disk quota as per ++ the soft limit. ++ If *over = 1, the number of bytes *over* quota. ++ -1 is returned on an error looking up quota ++ 0 is returned if there is no quota ++ ++BUG: If there's more than 2.1Gb free this function will break ++ ----*/ ++long ++disk_quota(path, over) ++ char *path; ++ int *over; ++{ ++ return(0L); ++} ++#endif /* USE_QUOTAS */ ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Read whole file into memory ++ ++ Args: filename -- path name of file to read ++ ++ Result: Returns pointer to malloced memory with the contents of the file ++ or NULL ++ ++This won't work very well if the file has NULLs in it and is mostly ++intended for fairly small text files. ++ ----*/ ++char * ++read_file(filename) ++ char *filename; ++{ ++ int fd; ++ struct stat statbuf; ++ char *buf; ++ int nb; ++ ++ fd = open(filename, O_RDONLY); ++ if(fd < 0) ++ return((char *)NULL); ++ ++ fstat(fd, &statbuf); ++ ++ buf = fs_get((size_t)statbuf.st_size + 1); ++ ++ /* ++ * On some systems might have to loop here, if one read isn't guaranteed ++ * to get the whole thing. ++ */ ++ if((nb = read(fd, buf, (int)statbuf.st_size)) < 0) ++ fs_give((void **)&buf); /* NULL's buf */ ++ else ++ buf[nb] = '\0'; ++ ++ close(fd); ++ return(buf); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Create a temporary file, the name of which we don't care about ++and that goes away when it is closed. Just like ANSI C tmpfile. ++ ----*/ ++FILE * ++create_tmpfile() ++{ ++ return(tmpfile()); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Abort with a core dump ++ ----*/ ++void ++coredump() ++{ ++ abort(); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Call system gethostname ++ ++ Args: hostname -- buffer to return host name in ++ size -- Size of buffer hostname is to be returned in ++ ++ Result: returns 0 if the hostname is correctly set, ++ -1 if not (and errno is set). ++ ----*/ ++hostname(hostname,size) ++ char *hostname; ++ int size; ++{ ++ return(gethostname(hostname, size)); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Get the current host and domain names ++ ++ Args: hostname -- buffer to return the hostname in ++ hsize -- size of buffer above ++ domainname -- buffer to return domain name in ++ dsize -- size of buffer above ++ ++ Result: The system host and domain names are returned. If the full host ++ name is akbar.cac.washington.edu then the domainname is ++ cac.washington.edu. ++ ++On Internet connected hosts this look up uses /etc/hosts and DNS to ++figure all this out. On other less well connected machines some other ++file may be read. If there is no notion of a domain name the domain ++name may be left blank. On a PC where there really isn't a host name ++this should return blank strings. The .pinerc will take care of ++configuring the domain names. That is, this should only return the ++native system's idea of what the names are if the system has such ++a concept. ++ ----*/ ++void ++getdomainnames(hostname, hsize, domainname, dsize) ++ char *hostname, *domainname; ++ int hsize, dsize; ++{ ++ char *dn, hname[MAX_ADDRESS+1]; ++ struct hostent *he; ++ char **alias; ++ char *maybe = NULL; ++ ++ gethostname(hname, MAX_ADDRESS); ++ he = gethostbyname(hname); ++ hostname[0] = '\0'; ++ ++ if(he == NULL) ++ strncpy(hostname, hname, hsize-1); ++ else{ ++ /* ++ * If no dot in hostname it may be the case that there ++ * is an alias which is really the fully-qualified ++ * hostname. This could happen if the administrator has ++ * (incorrectly) put the unqualified name first in the ++ * hosts file, for example. The problem with looking for ++ * an alias with a dot is that now we're guessing, since ++ * the aliases aren't supposed to be the official hostname. ++ * We'll compromise and only use an alias if the primary ++ * name has no dot and exactly one of the aliases has a ++ * dot. ++ */ ++ strncpy(hostname, he->h_name, hsize-1); ++ if(strindex(hostname, '.') == NULL){ /* no dot in hostname */ ++ for(alias = he->h_aliases; *alias; alias++){ ++ if(strindex(*alias, '.') != NULL){ /* found one */ ++ if(maybe){ /* oops, this is the second one */ ++ maybe = NULL; ++ break; ++ } ++ else ++ maybe = *alias; ++ } + } - if((n = folder_lister_prev(fs)) >= 0) - fs->folder_index = n; ++ ++ if(maybe) ++ strncpy(hostname, maybe, hsize-1); ++ } ++ } ++ ++ hostname[hsize-1] = '\0'; ++ ++ ++ if((dn = strindex(hostname, '.')) != NULL) ++ strncpy(domainname, dn+1, dsize-1); ++ else ++ strncpy(domainname, hostname, dsize-1); ++ ++ domainname[dsize-1] = '\0'; ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Return canonical form of host name ala c-client (UNIX version). ++ ++ Args: host -- The host name ++ ++ Result: Canonical form, or input argument (worst case) ++ ----*/ ++char * ++canonical_name(host) ++ char *host; ++{ ++ struct hostent *hent; ++ char hostname[MAILTMPLEN]; ++ char tmp[MAILTMPLEN]; ++ extern char *lcase(); ++ /* domain literal is easy */ ++ if (host[0] == '[' && host[(strlen (host))-1] == ']') ++ return host; ++ ++ strcpy (hostname,host); /* UNIX requires lowercase */ ++ /* lookup name, return canonical form */ ++ return (hent = gethostbyname (lcase (strcpy (tmp,host)))) ? ++ hent->h_name : host; ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ This routine returns 1 if job control is available. Note, thiis ++ could be some type of fake job control. It doesn't have to be ++ real BSD-style job control. ++ ----*/ ++have_job_control() ++{ ++ return 1; ++} ++ ++ ++/*---------------------------------------------------------------------- ++ If we don't have job control, this routine is never called. ++ ----*/ ++stop_process() ++{ ++ SigType (*save_usr2) SIG_PROTO((int)); ++ ++ /* ++ * Since we can't respond to KOD while stopped, the process that sent ++ * the KOD is going to go read-only. Therefore, we can safely ignore ++ * any KODs that come in before we are ready to respond... ++ */ ++ save_usr2 = signal(SIGUSR2, SIG_IGN); ++ kill(0, SIGSTOP); ++ (void)signal(SIGUSR2, save_usr2); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Return string describing the error ++ ++ Args: errnumber -- The system error number (errno) ++ ++ Result: long string describing the error is returned ++ ----*/ ++char * ++error_description(errnumber) ++ int errnumber; ++{ ++ static char buffer[50+1]; ++ ++ if(errnumber >= 0 && errnumber < sys_nerr) ++ sprintf(buffer, "%.*s", 50, sys_errlist[errnumber]); ++ else ++ sprintf(buffer, "Unknown error #%d", errnumber); ++ ++ return ( (char *) buffer); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Pull the name out of the gcos field if we have that sort of /etc/passwd ++ ++ Args: gcos_field -- The long name or GCOS field to be parsed ++ logname -- Replaces occurances of & with logname string ++ ++ Result: returns pointer to buffer with name ++ ----*/ ++static char * ++gcos_name(gcos_field, logname) ++ char *logname, *gcos_field; ++{ ++ static char fullname[MAX_FULLNAME+1]; ++ register char *fncp, *gcoscp, *lncp, *end; ++ ++ /* full name is all chars up to first ',' (or whole gcos, if no ',') */ ++ /* replace any & with logname in upper case */ ++ ++ for(fncp = fullname, gcoscp= gcos_field, end = fullname + MAX_FULLNAME - 1; ++ (*gcoscp != ',' && *gcoscp != '\0' && fncp != end); ++ gcoscp++) { ++ ++ if(*gcoscp == '&') { ++ for(lncp = logname; *lncp; fncp++, lncp++) ++ *fncp = toupper((unsigned char)(*lncp)); ++ } else { ++ *fncp++ = *gcoscp; ++ } ++ } ++ ++ *fncp = '\0'; ++ return(fullname); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Fill in homedir, login, and fullname for the logged in user. ++ These are all pointers to static storage so need to be copied ++ in the caller. ++ ++ Args: ui -- struct pointer to pass back answers ++ ++ Result: fills in the fields ++ ----*/ ++void ++get_user_info(ui) ++ struct user_info *ui; ++{ ++ struct passwd *unix_pwd; ++ ++ unix_pwd = getpwuid(getuid()); ++ if(unix_pwd == NULL) { ++ ui->homedir = cpystr(""); ++ ui->login = cpystr(""); ++ ui->fullname = cpystr(""); ++ }else { ++ ui->homedir = cpystr(unix_pwd->pw_dir); ++ ui->login = cpystr(unix_pwd->pw_name); ++ ui->fullname = cpystr(gcos_name(unix_pwd->pw_gecos, unix_pwd->pw_name)); ++ } ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Look up a userid on the local system and return rfc822 address ++ ++ Args: name -- possible login name on local system ++ ++ Result: returns NULL or pointer to alloc'd string rfc822 address. ++ ----*/ ++char * ++local_name_lookup(name) ++ char *name; ++{ ++ struct passwd *pw = getpwnam(name); ++ ++ if(pw == NULL) ++ return((char *)NULL); ++ ++ return(cpystr(gcos_name(pw->pw_gecos, name))); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Call the system to change the passwd ++ ++It would be nice to talk to the passwd program via a pipe or ptty so the ++user interface could be consistent, but we can't count on the the prompts ++and responses from the passwd program to be regular so we just let the user ++type at the passwd program with some screen space, hope he doesn't scroll ++off the top and repaint when he's done. ++ ----*/ ++change_passwd() ++{ ++ char cmd_buf[100]; ++ ++ ClearLines(1, ps_global->ttyo->screen_rows - 1); ++ ++ MoveCursor(5, 0); ++ fflush(stdout); ++ ++ PineRaw(0); ++ strcpy(cmd_buf, PASSWD_PROG); ++ system(cmd_buf); ++ sleep(3); ++ PineRaw(1); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Can we display this type/subtype? ++ ++ Args: type -- the MIME type to check ++ subtype -- the MIME subtype ++ params -- parameters ++ use_viewer -- tell caller he should run external viewer cmd to view ++ ++ Result: Returns: ++ ++ MCD_NONE if we can't display this type at all ++ MCD_INTERNAL if we can display it internally ++ MCD_EXTERNAL if it can be displayed via an external viewer ++ ++ ----*/ ++mime_can_display(type, subtype, params) ++ int type; ++ char *subtype; ++ PARAMETER *params; ++{ ++ return((mailcap_can_display(type, subtype, params) ++ ? MCD_EXTERNAL : MCD_NONE) ++ | ((type == TYPETEXT || type == TYPEMESSAGE ++ || MIME_VCARD(type,subtype)) ++ ? MCD_INTERNAL : MCD_NONE)); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ This is just a call to the ANSI C fgetpos function. ++ ----*/ ++fget_pos(stream, ptr) ++FILE *stream; ++fpos_t *ptr; ++{ ++ return(fgetpos(stream, ptr)); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ This is just a call to the ANSI C fsetpos function. ++ ----*/ ++fset_pos(stream, ptr) ++FILE *stream; ++fpos_t *ptr; ++{ ++ return(fsetpos(stream, ptr)); ++} ++ ++ ++ ++/*====================================================================== ++ pipe ++ ++ Initiate I/O to and from a process. These functions are similar to ++ popen and pclose, but both an incoming stream and an output file are ++ provided. ++ ++ ====*/ ++ ++#ifndef STDIN_FILENO ++#define STDIN_FILENO 0 ++#endif ++#ifndef STDOUT_FILENO ++#define STDOUT_FILENO 1 ++#endif ++#ifndef STDERR_FILENO ++#define STDERR_FILENO 2 ++#endif ++ ++ ++/* ++ * Defs to help fish child's exit status out of wait(2) ++ */ ++#ifdef HAVE_WAIT_UNION ++#define WaitType union wait ++#ifndef WIFEXITED ++#define WIFEXITED(X) (!(X).w_termsig) /* child exit by choice */ ++#endif ++#ifndef WEXITSTATUS ++#define WEXITSTATUS(X) (X).w_retcode /* childs chosen exit value */ ++#endif ++#else ++#define WaitType int ++#ifndef WIFEXITED ++#define WIFEXITED(X) (!((X) & 0xff)) /* low bits tell how it died */ ++#endif ++#ifndef WEXITSTATUS ++#define WEXITSTATUS(X) (((X) >> 8) & 0xff) /* high bits tell exit value */ ++#endif ++#endif ++ ++ ++/* ++ * Global's to helpsignal handler tell us child's status has changed... ++ */ ++short child_signalled; ++short child_jump = 0; ++jmp_buf child_state; ++ ++ ++/* ++ * Internal Protos ++ */ ++void pipe_error_cleanup PROTO((PIPE_S **, char *, char *, char *)); ++void zot_pipe PROTO((PIPE_S **)); ++ ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Spawn a child process and optionally connect read/write pipes to it ++ ++ Args: command -- string to hand the shell ++ outfile -- address of pointer containing file to receive output ++ errfile -- address of pointer containing file to receive error output ++ mode -- mode for type of shell, signal protection etc... ++ Returns: pointer to alloc'd PIPE_S on success, NULL otherwise ++ ++ The outfile is either NULL, a pointer to a NULL value, or a pointer ++ to the requested name for the output file. In the pointer-to-NULL case ++ the caller doesn't care about the name, but wants to see the pipe's ++ results so we make one up. It's up to the caller to make sure the ++ free storage containing the name is cleaned up. ++ ++ Mode bits serve several purposes. ++ PIPE_WRITE tells us we need to open a pipe to write the child's ++ stdin. ++ PIPE_READ tells us we need to open a pipe to read from the child's ++ stdout/stderr. *NOTE* Having neither of the above set means ++ we're not setting up any pipes, just forking the child and exec'ing ++ the command. Also, this takes precedence over any named outfile. ++ PIPE_STDERR means we're to tie the childs stderr to the same place ++ stdout is going. *NOTE* This only makes sense then if PIPE_READ ++ or an outfile is provided. Also, this takes precedence over any ++ named errfile. ++ PIPE_PROT means to protect the child from the usual nasty signals ++ that might cause premature death. Otherwise, the default signals are ++ set so the child can deal with the nasty signals in its own way. ++ PIPE_NOSHELL means we're to exec the command without the aid of ++ a system shell. *NOTE* This negates the affect of PIPE_USER. ++ PIPE_USER means we're to try executing the command in the user's ++ shell. Right now we only look in the environment, but that may get ++ more sophisticated later. ++ PIPE_RESET means we reset the terminal mode to what it was before ++ we started pine and then exec the command. ++ ----*/ ++PIPE_S * ++open_system_pipe(command, outfile, errfile, mode) ++ char *command; ++ char **outfile, **errfile; ++ int mode; ++{ ++ PIPE_S *syspipe = NULL; ++ char shellpath[32], *shell; ++ int p[2], oparentd = -1, ochildd = -1, iparentd = -1, ichildd = -1; ++ ++ dprint(5, (debugfile, "Opening pipe: \"%s\" (%s%s%s%s%s%s)\n", command, ++ (mode & PIPE_WRITE) ? "W":"", (mode & PIPE_READ) ? "R":"", ++ (mode & PIPE_NOSHELL) ? "N":"", (mode & PIPE_PROT) ? "P":"", ++ (mode & PIPE_USER) ? "U":"", (mode & PIPE_RESET) ? "T":"")); ++ ++ syspipe = (PIPE_S *)fs_get(sizeof(PIPE_S)); ++ memset(syspipe, 0, sizeof(PIPE_S)); ++ ++ /* ++ * If we're not using the shell's command parsing smarts, build ++ * argv by hand... ++ */ ++ if(mode & PIPE_NOSHELL){ ++ char **ap, *p; ++ size_t n; ++ ++ /* parse the arguments into argv */ ++ for(p = command; *p && isspace((unsigned char)(*p)); p++) ++ ; /* swallow leading ws */ ++ ++ if(*p){ ++ syspipe->args = cpystr(p); ++ } ++ else{ ++ pipe_error_cleanup(&syspipe, "<null>", "execute", ++ "No command name found"); ++ return(NULL); ++ } ++ ++ for(p = syspipe->args, n = 2; *p; p++) /* count the args */ ++ if(isspace((unsigned char)(*p)) ++ && *(p+1) && !isspace((unsigned char)(*(p+1)))) ++ n++; ++ ++ syspipe->argv = ap = (char **)fs_get(n * sizeof(char *)); ++ memset(syspipe->argv, 0, n * sizeof(char *)); ++ ++ for(p = syspipe->args; *p; ){ /* collect args */ ++ while(*p && isspace((unsigned char)(*p))) ++ *p++ = '\0'; ++ ++ *ap++ = (*p) ? p : NULL; ++ while(*p && !isspace((unsigned char)(*p))) ++ p++; ++ } ++ ++ /* make sure argv[0] exists in $PATH */ ++ if(can_access_in_path(getenv("PATH"), syspipe->argv[0], ++ EXECUTE_ACCESS) < 0){ ++ pipe_error_cleanup(&syspipe, syspipe->argv[0], "access", ++ error_description(errno)); ++ return(NULL); ++ } ++ } ++ ++ /* fill in any output filenames */ ++ if(!(mode & PIPE_READ)){ ++ if(outfile && !*outfile) ++ *outfile = temp_nam(NULL, "pine_p"); /* asked for, but not named? */ ++ ++ if(errfile && !*errfile) ++ *errfile = temp_nam(NULL, "pine_p"); /* ditto */ ++ } ++ ++ /* create pipes */ ++ if(mode & (PIPE_WRITE | PIPE_READ)){ ++ if(mode & PIPE_WRITE){ ++ pipe(p); /* alloc pipe to write child */ ++ oparentd = p[STDOUT_FILENO]; ++ ichildd = p[STDIN_FILENO]; ++ } ++ ++ if(mode & PIPE_READ){ ++ pipe(p); /* alloc pipe to read child */ ++ iparentd = p[STDIN_FILENO]; ++ ochildd = p[STDOUT_FILENO]; ++ } ++ } ++ else if(!(mode & PIPE_SILENT)){ ++ flush_status_messages(0); /* just clean up display */ ++ ClearScreen(); ++ fflush(stdout); ++ } ++ ++ if((syspipe->mode = mode) & PIPE_RESET) ++ PineRaw(0); ++ ++#ifdef SIGCHLD ++ /* ++ * Prepare for demise of child. Use SIGCHLD if it's available so ++ * we can do useful things, like keep the IMAP stream alive, while ++ * we're waiting on the child. ++ */ ++ child_signalled = child_jump = 0; ++#endif ++ ++ if((syspipe->pid = vfork()) == 0){ ++ /* reset child's handlers in requested fashion... */ ++ (void)signal(SIGINT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL); ++ (void)signal(SIGQUIT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL); ++ (void)signal(SIGHUP, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL); ++#ifdef SIGCHLD ++ (void) signal(SIGCHLD, SIG_DFL); ++#endif ++ ++ /* if parent isn't reading, and we have a filename to write */ ++ if(!(mode & PIPE_READ) && outfile){ /* connect output to file */ ++ int output = creat(*outfile, 0600); ++ dup2(output, STDOUT_FILENO); ++ if(mode & PIPE_STDERR) ++ dup2(output, STDERR_FILENO); ++ else if(errfile) ++ dup2(creat(*errfile, 0600), STDERR_FILENO); ++ } ++ ++ if(mode & PIPE_WRITE){ /* connect process input */ ++ close(oparentd); ++ dup2(ichildd, STDIN_FILENO); /* tie stdin to pipe */ ++ close(ichildd); ++ } ++ ++ if(mode & PIPE_READ){ /* connect process output */ ++ close(iparentd); ++ dup2(ochildd, STDOUT_FILENO); /* tie std{out,err} to pipe */ ++ if(mode & PIPE_STDERR) ++ dup2(ochildd, STDERR_FILENO); ++ else if(errfile) ++ dup2(creat(*errfile, 0600), STDERR_FILENO); ++ ++ close(ochildd); ++ } ++ ++ if(mode & PIPE_NOSHELL){ ++ execvp(syspipe->argv[0], syspipe->argv); ++ } ++ else{ ++ if(mode & PIPE_USER){ ++ char *env, *sh; ++ if((env = getenv("SHELL")) && (sh = strrchr(env, '/'))){ ++ shell = sh + 1; ++ strcpy(shellpath, env); ++ } ++ else{ ++ shell = "csh"; ++ strcpy(shellpath, "/bin/csh"); ++ } ++ } ++ else{ ++ shell = "sh"; ++ strcpy(shellpath, "/bin/sh"); ++ } ++ ++ execl(shellpath, shell, command ? "-c" : 0, command, 0); ++ } ++ ++ fprintf(stderr, "Can't exec %s\nReason: %s", ++ command, error_description(errno)); ++ _exit(-1); ++ } ++ ++ if(syspipe->pid > 0){ ++ syspipe->isig = signal(SIGINT, SIG_IGN); /* Reset handlers to make */ ++ syspipe->qsig = signal(SIGQUIT, SIG_IGN); /* sure we don't come to */ ++ syspipe->hsig = signal(SIGHUP, SIG_IGN); /* a premature end... */ ++ ++ if(mode & PIPE_WRITE){ ++ close(ichildd); ++ if(mode & PIPE_DESC) ++ syspipe->out.d = oparentd; ++ else ++ syspipe->out.f = fdopen(oparentd, "w"); ++ } ++ ++ if(mode & PIPE_READ){ ++ close(ochildd); ++ if(mode & PIPE_DESC) ++ syspipe->in.d = iparentd; ++ else ++ syspipe->in.f = fdopen(iparentd, "r"); ++ } ++ ++ dprint(5, (debugfile, "PID: %d, COMMAND: %s\n",syspipe->pid,command)); ++ } ++ else{ ++ if(mode & (PIPE_WRITE | PIPE_READ)){ ++ if(mode & PIPE_WRITE){ ++ close(oparentd); ++ close(ichildd); ++ } ++ ++ if(mode & PIPE_READ){ ++ close(iparentd); ++ close(ochildd); ++ } ++ } ++ else if(!(mode & PIPE_SILENT)){ ++ ClearScreen(); ++ ps_global->mangled_screen = 1; ++ } ++ ++ if(mode & PIPE_RESET) ++ PineRaw(1); ++ ++#ifdef SIGCHLD ++ (void) signal(SIGCHLD, SIG_DFL); ++#endif ++ if(outfile) ++ fs_give((void **) outfile); ++ ++ pipe_error_cleanup(&syspipe, command, "fork",error_description(errno)); ++ } ++ ++ return(syspipe); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Write appropriate error messages and cleanup after pipe error ++ ++ Args: syspipe -- address of pointer to struct to clean up ++ cmd -- command we were trying to exec ++ op -- operation leading up to the exec ++ res -- result of that operation ++ ++ ----*/ ++void ++pipe_error_cleanup(syspipe, cmd, op, res) ++ PIPE_S **syspipe; ++ char *cmd, *op, *res; ++{ ++ q_status_message3(SM_ORDER, 3, 3, "Pipe can't %s \"%.20s\": %s", ++ op, cmd, res); ++ dprint(1, (debugfile, "* * PIPE CAN'T %s(%s): %s\n", op, cmd, res)); ++ zot_pipe(syspipe); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Free resources associated with the given pipe struct ++ ++ Args: syspipe -- address of pointer to struct to clean up ++ ++ ----*/ ++void ++zot_pipe(syspipe) ++ PIPE_S **syspipe; ++{ ++ if((*syspipe)->args) ++ fs_give((void **) &(*syspipe)->args); ++ ++ if((*syspipe)->argv) ++ fs_give((void **) &(*syspipe)->argv); ++ ++ if((*syspipe)->tmp) ++ fs_give((void **) &(*syspipe)->tmp); ++ ++ fs_give((void **)syspipe); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Close pipe previously allocated and wait for child's death ++ ++ Args: syspipe -- address of pointer to struct returned by open_system_pipe ++ Returns: returns exit status of child or -1 if invalid syspipe ++ ----*/ ++int ++close_system_pipe(syspipe) ++ PIPE_S **syspipe; ++{ ++ WaitType stat; ++ int status; ++ ++ if(!(syspipe && *syspipe)) ++ return(-1); ++ ++ if(((*syspipe)->mode) & PIPE_WRITE){ ++ if(((*syspipe)->mode) & PIPE_DESC){ ++ if((*syspipe)->out.d >= 0) ++ close((*syspipe)->out.d); ++ } ++ else if((*syspipe)->out.f) ++ fclose((*syspipe)->out.f); ++ } ++ ++ if(((*syspipe)->mode) & PIPE_READ){ ++ if(((*syspipe)->mode) & PIPE_DESC){ ++ if((*syspipe)->in.d >= 0) ++ close((*syspipe)->in.d); ++ } ++ else if((*syspipe)->in.f) ++ fclose((*syspipe)->in.f); ++ } ++ ++#ifdef SIGCHLD ++ { ++ SigType (*alarm_sig)(); ++ int old_cue = F_ON(F_SHOW_DELAY_CUE, ps_global); ++ ++ /* ++ * remember the current SIGALRM handler, and make sure it's ++ * installed when we're finished just in case the longjmp ++ * out of the SIGCHLD handler caused sleep() to lose it. ++ * Don't pay any attention to that man behind the curtain. ++ */ ++ alarm_sig = signal(SIGALRM, SIG_IGN); ++ (void) signal(SIGALRM, alarm_sig); ++ F_SET(F_SHOW_DELAY_CUE, ps_global, 0); ++ ps_global->noshow_timeout = 1; ++ while(!child_signalled){ ++ /* wake up and prod server */ ++ new_mail(0, 2, ((*syspipe)->mode & PIPE_RESET) ++ ? NM_NONE : NM_DEFER_SORT); ++ ++ if(!child_signalled){ ++ if(setjmp(child_state) == 0){ ++ child_jump = 1; /* prepare to wake up */ ++ sleep(600); /* give it 5mins to happend */ ++ } ++ else ++ our_sigunblock(SIGCHLD); ++ } ++ ++ child_jump = 0; ++ } ++ ++ ps_global->noshow_timeout = 0; ++ F_SET(F_SHOW_DELAY_CUE, ps_global, old_cue); ++ (void) signal(SIGALRM, alarm_sig); ++ } ++#endif ++ ++ /* ++ * Call c-client's pid reaper to wait() on the demise of our child, ++ * then fish out its exit status... ++ */ ++ grim_pid_reap_status((*syspipe)->pid, 0, &stat); ++ status = WIFEXITED(stat) ? WEXITSTATUS(stat) : -1; ++ ++ /* ++ * restore original handlers... ++ */ ++ (void)signal(SIGINT, (*syspipe)->isig); ++ (void)signal(SIGHUP, (*syspipe)->hsig); ++ (void)signal(SIGQUIT, (*syspipe)->qsig); ++ ++ if((*syspipe)->mode & PIPE_RESET) /* restore our tty modes */ ++ PineRaw(1); ++ ++ if(!((*syspipe)->mode & (PIPE_WRITE | PIPE_READ | PIPE_SILENT))){ ++ ClearScreen(); /* No I/O to forked child */ ++ ps_global->mangled_screen = 1; ++ } ++ ++ zot_pipe(syspipe); ++ ++ return(status); ++} ++ ++/*====================================================================== ++ post_reap ++ ++ Manage exit status collection of a child spawned to handle posting ++ ====*/ ++ ++ ++ ++#if defined(BACKGROUND_POST) && defined(SIGCHLD) ++/*---------------------------------------------------------------------- ++ Check to see if we have any posting processes to clean up after ++ ++ Args: none ++ Returns: any finished posting process reaped ++ ----*/ ++post_reap() ++{ ++ WaitType stat; ++ int r; ++ ++ if(ps_global->post && ps_global->post->pid){ ++ r = waitpid(ps_global->post->pid, &stat, WNOHANG); ++ if(r == ps_global->post->pid){ ++ ps_global->post->status = WIFEXITED(stat) ? WEXITSTATUS(stat) : -1; ++ ps_global->post->pid = 0; ++ return(1); ++ } ++ else if(r < 0 && errno != EINTR){ /* pid's become bogus?? */ ++ fs_give((void **) &ps_global->post); ++ } ++ } ++ ++ return(0); ++} ++#endif ++ ++/*---------------------------------------------------------------------- ++ Routines used to hand off messages to local agents for sending/posting ++ ++ The two exported routines are: ++ ++ 1) smtp_command() -- used to get local transport agent to invoke ++ 2) post_handoff() -- used to pass messages to local posting agent ++ ++ ----*/ ++ ++ ++ ++/* ++ * Protos for "sendmail" internal functions ++ */ ++static char *mta_parse_post PROTO((METAENV *, BODY *, char *, char *)); ++static long pine_pipe_soutr_nl PROTO((void *, char *)); ++ ++ ++ ++/* ---------------------------------------------------------------------- ++ Figure out command to start local SMTP agent ++ ++ Args: errbuf -- buffer for reporting errors (assumed non-NULL) ++ ++ Returns an alloc'd copy of the local SMTP agent invocation or NULL ++ ++ ----*/ ++char * ++smtp_command(errbuf) ++ char *errbuf; ++{ ++#if defined(SENDMAIL) && defined(SENDMAILFLAGS) ++ char tmp[256]; ++ ++ sprintf(tmp, "%s %s", SENDMAIL, SENDMAILFLAGS); ++ return(cpystr(tmp)); ++#else ++ strcpy(errbuf, "No default posting command."); ++ return(NULL); ++#endif ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Hand off given message to local posting agent ++ ++ Args: envelope -- The envelope for the BCC and debugging ++ header -- The text of the message header ++ errbuf -- buffer for reporting errors (assumed non-NULL) ++ ++ ----*/ ++int ++mta_handoff(header, body, errbuf) ++ METAENV *header; ++ BODY *body; ++ char *errbuf; ++{ ++ char cmd_buf[256], *cmd = NULL; ++ ++ /* ++ * A bit of complicated policy implemented here. ++ * There are two posting variables sendmail-path and smtp-server. ++ * Precedence is in that order. ++ * They can be set one of 4 ways: fixed, command-line, user, or globally. ++ * Precedence is in that order. ++ * Said differently, the order goes something like what's below. ++ * ++ * NOTE: the fixed/command-line/user precendence handling is also ++ * indicated by what's pointed to by ps_global->VAR_*, but since ++ * that also includes the global defaults, it's not sufficient. ++ */ ++ ++ if(ps_global->FIX_SENDMAIL_PATH ++ && ps_global->FIX_SENDMAIL_PATH[0]){ ++ cmd = ps_global->FIX_SENDMAIL_PATH; ++ } ++ else if(!(ps_global->FIX_SMTP_SERVER ++ && ps_global->FIX_SMTP_SERVER[0])){ ++ if(ps_global->COM_SENDMAIL_PATH ++ && ps_global->COM_SENDMAIL_PATH[0]){ ++ cmd = ps_global->COM_SENDMAIL_PATH; ++ } ++ else if(!(ps_global->COM_SMTP_SERVER ++ && ps_global->COM_SMTP_SERVER[0])){ ++ if(ps_global->USR_SENDMAIL_PATH ++ && ps_global->USR_SENDMAIL_PATH[0]){ ++ cmd = ps_global->USR_SENDMAIL_PATH; ++ } ++ else if(!(ps_global->USR_SMTP_SERVER ++ && ps_global->USR_SMTP_SERVER[0])){ ++ if(ps_global->GLO_SENDMAIL_PATH ++ && ps_global->GLO_SENDMAIL_PATH[0]){ ++ cmd = ps_global->GLO_SENDMAIL_PATH; ++ } ++#ifdef DF_SENDMAIL_PATH ++ /* ++ * This defines the default method of posting. So, ++ * unless we're told otherwise use it... ++ */ ++ else if(!(ps_global->GLO_SMTP_SERVER ++ && ps_global->GLO_SMTP_SERVER[0])){ ++ strcpy(cmd = cmd_buf, DF_SENDMAIL_PATH); ++ } ++#endif ++ } ++ } ++ } ++ ++ *errbuf = '\0'; ++ if(cmd){ ++ dprint(4, (debugfile, "call_mailer via cmd: %s\n", cmd)); ++ ++ (void) mta_parse_post(header, body, cmd, errbuf); ++ return(1); ++ } ++ else ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Hand off given message to local posting agent ++ ++ Args: envelope -- The envelope for the BCC and debugging ++ header -- The text of the message header ++ errbuf -- buffer for reporting errors (assumed non-NULL) ++ ++ Fork off mailer process and pipe the message into it ++ Called to post news via Inews when NNTP is unavailable ++ ++ ----*/ ++char * ++post_handoff(header, body, errbuf) ++ METAENV *header; ++ BODY *body; ++ char *errbuf; ++{ ++ char *err = NULL; ++#ifdef SENDNEWS ++ char *s; ++ ++ if(s = strstr(header->env->date," (")) /* fix the date format for news */ ++ *s = '\0'; ++ ++ if(err = mta_parse_post(header, body, SENDNEWS, errbuf)) ++ sprintf(err = errbuf, "News not posted: \"%s\": %s", SENDNEWS, err); ++ ++ if(s) ++ *s = ' '; /* restore the date */ ++ ++#else /* !SENDNEWS */ /* this is the default case */ ++ sprintf(err = errbuf, "Can't post, NNTP-server must be defined!"); ++#endif /* !SENDNEWS */ ++ return(err); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Hand off message to local MTA; it parses recipients from 822 header ++ ++ Args: header -- struct containing header data ++ body -- struct containing message body data ++ cmd -- command to use for handoff (%s says where file should go) ++ errs -- pointer to buf to hold errors ++ ++ ----*/ ++static char * ++mta_parse_post(header, body, cmd, errs) ++ METAENV *header; ++ BODY *body; ++ char *cmd; ++ char *errs; ++{ ++ char *result = NULL; ++ PIPE_S *pipe; ++ ++ dprint(1, (debugfile, "=== mta_parse_post(%s) ===\n", cmd)); ++ ++ if(pipe = open_system_pipe(cmd, &result, NULL, ++ PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC)){ ++ if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl, ++ (TCPSTREAM *) pipe)) ++ strcpy(errs, "Error posting."); ++ ++ if(close_system_pipe(&pipe) && !*errs){ ++ sprintf(errs, "Posting program %s returned error", cmd); ++ if(result) ++ display_output_file(result, "POSTING ERRORS", errs, 1); ++ } ++ } ++ else ++ sprintf(errs, "Error running \"%s\"", cmd); ++ ++ if(result){ ++ unlink(result); ++ fs_give((void **)&result); ++ } ++ ++ return(*errs ? errs : NULL); ++} ++ ++ ++/* ++ * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our ++ * pipes rather than a tcp stream ++ */ ++static long ++pine_pipe_soutr_nl (stream,s) ++ void *stream; ++ char *s; ++{ ++ long rv = T; ++ char *p; ++ size_t n; ++ ++ while(*s && rv){ ++ if(n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) ++ while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n) ++ if(rv < 0){ ++ if(errno != EINTR){ ++ rv = 0; ++ break; ++ } ++ } ++ else{ ++ s += rv; ++ n -= rv; ++ } ++ ++ if(p && rv){ ++ s = p + 2; /* write UNIX EOL */ ++ while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1) ++ if(rv < 0 && errno != EINTR){ ++ rv = 0; ++ break; ++ } ++ } ++ else ++ break; ++ } ++ ++ return(rv); ++} ++ ++/* ---------------------------------------------------------------------- ++ Execute the given mailcap command ++ ++ Args: cmd -- the command to execute ++ image_file -- the file the data is in ++ needsterminal -- does this command want to take over the terminal? ++ ----*/ ++void ++exec_mailcap_cmd(cmd, image_file, needsterminal) ++char *cmd; ++char *image_file; ++int needsterminal; ++{ ++ char *command = NULL, ++ *result_file = NULL, ++ *p; ++ char **r_file_h; ++ PIPE_S *syspipe; ++ int mode; ++ ++ p = command = (char *)fs_get((32 + strlen(cmd) + (2*strlen(image_file))) ++ * sizeof(char)); ++ if(!needsterminal) /* put in background if it doesn't need terminal */ ++ *p++ = '('; ++ sprintf(p, "%s ; rm -f %s", cmd, image_file); ++ p += strlen(p); ++ if(!needsterminal){ ++ *p++ = ')'; ++ *p++ = ' '; ++ *p++ = '&'; ++ } ++ *p++ = '\n'; ++ *p = '\0'; ++ dprint(9, (debugfile, "exec_mailcap_cmd: command=%s\n", command)); ++ ++ mode = PIPE_RESET; ++ if(needsterminal == 1) ++ r_file_h = NULL; ++ else{ ++ mode |= PIPE_WRITE | PIPE_STDERR; ++ result_file = temp_nam(NULL, "pine_cmd"); ++ r_file_h = &result_file; ++ } ++ ++ if(syspipe = open_system_pipe(command, r_file_h, NULL, mode)){ ++ close_system_pipe(&syspipe); ++ if(needsterminal == 1) ++ q_status_message(SM_ORDER, 0, 4, "VIEWER 命令完成"); ++ else if(needsterminal == 2) ++ display_output_file(result_file, "VIEWER", " command result", 1); ++ else ++ display_output_file(result_file, "VIEWER", " command launched", 1); ++ } ++ else ++ q_status_message1(SM_ORDER, 3, 4, "無法起始命令:%s", cmd); ++ ++ fs_give((void **)&command); ++ if(result_file) ++ fs_give((void **)&result_file); ++} ++ ++ ++/* ---------------------------------------------------------------------- ++ Execute the given mailcap test= cmd ++ ++ Args: cmd -- command to execute ++ Returns exit status ++ ++ ----*/ ++int ++exec_mailcap_test_cmd(cmd) ++ char *cmd; ++{ ++ PIPE_S *syspipe; ++ ++ return((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_SILENT)) ++ ? close_system_pipe(&syspipe) : -1); ++} ++ ++ ++ ++/*====================================================================== ++ print routines ++ ++ Functions having to do with printing on paper and forking of spoolers ++ ++ In general one calls open_printer() to start printing. One of ++ the little print functions to send a line or string, and then ++ call print_end() when complete. This takes care of forking off a spooler ++ and piping the stuff down it. No handles or anything here because there's ++ only one printer open at a time. ++ ++ ====*/ ++ ++ ++ ++static char *trailer; /* so both open and close_printer can see it */ ++ ++/*---------------------------------------------------------------------- ++ Open the printer ++ ++ Args: desc -- Description of item to print. Should have one trailing blank. ++ ++ Return value: < 0 is a failure. ++ 0 a success. ++ ++This does most of the work of popen so we can save the standard output of the ++command we execute and send it back to the user. ++ ----*/ ++int ++open_printer(desc) ++ char *desc; ++{ ++ char command[201], prompt[200]; ++ int cmd, rc, just_one; ++ char *p, *init, *nick; ++ char aname[100]; ++ char *printer; ++ int done = 0, i, lastprinter, cur_printer = 0; ++ HelpType help; ++ char **list; ++ static ESCKEY_S ekey[] = { ++ {'y', 'y', "Y", "是"}, ++ {'n', 'n', "N", "否"}, ++ {ctrl('P'), 10, "^P", "前一印表機"}, ++ {ctrl('N'), 11, "^N", "下一印表機"}, ++ {-2, 0, NULL, NULL}, ++ {'c', 'c', "C", "自定印表機"}, ++ {KEY_UP, 10, "", ""}, ++ {KEY_DOWN, 11, "", ""}, ++ {-1, 0, NULL, NULL}}; ++#define PREV_KEY 2 ++#define NEXT_KEY 3 ++#define CUSTOM_KEY 5 ++#define UP_KEY 6 ++#define DOWN_KEY 7 ++ ++ trailer = NULL; ++ init = NULL; ++ nick = NULL; ++ command[200] = '\0'; ++ ++ if(ps_global->VAR_PRINTER == NULL){ ++ q_status_message(SM_ORDER | SM_DING, 3, 5, ++ "尚未選擇印表機。請用主選單中的設定來選擇。"); ++ return(-1); ++ } ++ ++ /* Is there just one print command available? */ ++ just_one = (ps_global->printer_category!=3&&ps_global->printer_category!=2) ++ || (ps_global->printer_category == 2 ++ && !(ps_global->VAR_STANDARD_PRINTER ++ && ps_global->VAR_STANDARD_PRINTER[0] ++ && ps_global->VAR_STANDARD_PRINTER[1])) ++ || (ps_global->printer_category == 3 ++ && !(ps_global->VAR_PERSONAL_PRINT_COMMAND ++ && ps_global->VAR_PERSONAL_PRINT_COMMAND[0] ++ && ps_global->VAR_PERSONAL_PRINT_COMMAND[1])); ++ ++ if(F_ON(F_CUSTOM_PRINT, ps_global)) ++ ekey[CUSTOM_KEY].ch = 'c'; /* turn this key on */ ++ else ++ ekey[CUSTOM_KEY].ch = -2; /* turn this key off */ ++ ++ if(just_one){ ++ ekey[PREV_KEY].ch = -2; /* turn these keys off */ ++ ekey[NEXT_KEY].ch = -2; ++ ekey[UP_KEY].ch = -2; ++ ekey[DOWN_KEY].ch = -2; ++ } ++ else{ ++ ekey[PREV_KEY].ch = ctrl('P'); /* turn these keys on */ ++ ekey[NEXT_KEY].ch = ctrl('N'); ++ ekey[UP_KEY].ch = KEY_UP; ++ ekey[DOWN_KEY].ch = KEY_DOWN; ++ /* ++ * count how many printers in list and find the default in the list ++ */ ++ if(ps_global->printer_category == 2) ++ list = ps_global->VAR_STANDARD_PRINTER; ++ else ++ list = ps_global->VAR_PERSONAL_PRINT_COMMAND; ++ ++ for(i = 0; list[i]; i++) ++ if(strcmp(ps_global->VAR_PRINTER, list[i]) == 0) ++ cur_printer = i; ++ ++ lastprinter = i - 1; ++ } ++ ++ help = NO_HELP; ++ ps_global->mangled_footer = 1; ++ ++ while(!done){ ++ if(init) ++ fs_give((void **)&init); ++ ++ if(trailer) ++ fs_give((void **)&trailer); ++ ++ if(just_one) ++ printer = ps_global->VAR_PRINTER; ++ else ++ printer = list[cur_printer]; ++ ++ parse_printer(printer, &nick, &p, &init, &trailer, NULL, NULL); ++ strncpy(command, p, 200); ++ fs_give((void **)&p); ++ sprintf(prompt, "Print %.50s%susing \"%.50s\" ? ", ++ desc ? desc : "", ++ (desc && *desc && desc[strlen(desc) - 1] != ' ') ? " " : "", ++ *nick ? nick : command); ++ ++ fs_give((void **)&nick); ++ ++ cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ++ ekey, 'y', 'x', help, RB_NORM); ++ ++ switch(cmd){ ++ case 'y': ++ q_status_message1(SM_ORDER, 0, 9, ++ "正以 \"%s\" 命令列印中", command); ++ done++; ++ break; ++ ++ case 10: ++ cur_printer = (cur_printer>0) ++ ? (cur_printer-1) ++ : lastprinter; ++ break; ++ ++ case 11: ++ cur_printer = (cur_printer<lastprinter) ++ ? (cur_printer+1) ++ : 0; ++ break; ++ ++ case 'n': ++ case 'x': ++ done++; ++ break; ++ ++ case 'c': ++ done++; ++ break; ++ ++ default: ++ break; ++ } ++ } ++ ++ if(cmd == 'c'){ ++ if(init) ++ fs_give((void **)&init); ++ ++ if(trailer) ++ fs_give((void **)&trailer); ++ ++ sprintf(prompt, "輸入命令:"); ++ command[0] = '\0'; ++ rc = 1; ++ help = NO_HELP; ++ while(rc){ ++ int flags = OE_APPEND_CURRENT; ++ ++ rc = optionally_enter(command, -FOOTER_ROWS(ps_global), 0, ++ 200, prompt, NULL, help, &flags); ++ ++ if(rc == 1){ ++ cmd = 'x'; ++ rc = 0; ++ } ++ else if(rc == 3) ++ help = (help == NO_HELP) ? h_custom_print : NO_HELP; ++ else if(rc == 0){ ++ removing_trailing_white_space(command); ++ removing_leading_white_space(command); ++ q_status_message1(SM_ORDER, 0, 9, ++ "正以 \"%s\" 命令列印中", command); ++ } ++ } ++ } ++ ++ if(cmd == 'x' || cmd == 'n'){ ++ q_status_message(SM_ORDER, 0, 2, "取消列印"); ++ if(init) ++ fs_give((void **)&init); ++ ++ if(trailer) ++ fs_give((void **)&trailer); ++ ++ return(-1); ++ } ++ ++ display_message('x'); ++ ++ ps_global->print = (PRINT_S *)fs_get(sizeof(PRINT_S)); ++ memset(ps_global->print, 0, sizeof(PRINT_S)); ++ ++ strcat(strcpy(aname, ANSI_PRINTER), "-no-formfeed"); ++ if(strucmp(command, ANSI_PRINTER) == 0 ++ || strucmp(command, aname) == 0){ ++ /*----------- Printer attached to ansi device ---------*/ ++ q_status_message(SM_ORDER, 0, 9, ++ "正列印至桌上印表機..."); ++ display_message('x'); ++ xonxoff_proc(1); /* make sure XON/XOFF used */ ++ crlf_proc(1); /* AND LF->CR xlation */ ++ fputs("\033[5i", stdout); ++ ps_global->print->fp = stdout; ++ if(strucmp(command, ANSI_PRINTER) == 0){ ++ /* put formfeed at the end of the trailer string */ ++ if(trailer){ ++ int len = strlen(trailer); ++ ++ fs_resize((void **)&trailer, len+2); ++ trailer[len] = '\f'; ++ trailer[len+1] = '\0'; ++ } ++ else ++ trailer = cpystr("\f"); ++ } ++ } ++ else{ ++ /*----------- Print by forking off a UNIX command ------------*/ ++ dprint(4, (debugfile, "Printing using command \"%s\"\n", command)); ++ ps_global->print->result = temp_nam(NULL, "pine_prt"); ++ if(ps_global->print->pipe = open_system_pipe(command, ++ &ps_global->print->result, NULL, ++ PIPE_WRITE | PIPE_STDERR)){ ++ ps_global->print->fp = ps_global->print->pipe->out.f; ++ } ++ else{ ++ fs_give((void **)&ps_global->print->result); ++ q_status_message1(SM_ORDER | SM_DING, 3, 4, ++ "印表機開啟錯誤:%s", ++ error_description(errno)); ++ dprint(2, (debugfile, "Error popening printer \"%s\"\n", ++ error_description(errno))); ++ if(init) ++ fs_give((void **)&init); ++ ++ if(trailer) ++ fs_give((void **)&trailer); ++ ++ return(-1); ++ } ++ } ++ ++ ps_global->print->err = 0; ++ if(init){ ++ if(*init) ++ fputs(init, ps_global->print->fp); ++ ++ fs_give((void **)&init); ++ } ++ ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Close printer ++ ++ If we're piping to a spooler close down the pipe and wait for the process ++to finish. If we're sending to an attached printer send the escape sequence. ++Also let the user know the result of the print ++ ----*/ ++void ++close_printer() ++{ ++ if(trailer){ ++ if(*trailer) ++ fputs(trailer, ps_global->print->fp); ++ ++ fs_give((void **)&trailer); ++ } ++ ++ if(ps_global->print->fp == stdout) { ++ fputs("\033[4i", stdout); ++ fflush(stdout); ++ if(F_OFF(F_PRESERVE_START_STOP, ps_global)) ++ xonxoff_proc(0); /* turn off XON/XOFF */ ++ ++ crlf_proc(0); /* turn off CF->LF xlantion */ ++ } else { ++ (void) close_system_pipe(&ps_global->print->pipe); ++ display_output_file(ps_global->print->result, "PRINT", NULL, 1); ++ fs_give((void **)&ps_global->print->result); ++ } ++ ++ fs_give((void **)&ps_global->print); ++ ++ q_status_message(SM_ASYNC, 0, 3, "列印指令完成"); ++ display_message('x'); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Print a single character ++ ++ Args: c -- char to print ++ Returns: 1 on success, 0 on ps_global->print->err ++ ----*/ ++int ++print_char(c) ++ int c; ++{ ++ if(!ps_global->print->err && putc(c, ps_global->print->fp) == EOF) ++ ps_global->print->err = 1; ++ ++ return(!ps_global->print->err); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Send a line of text to the printer ++ ++ Args: line -- Text to print ++ ++ ----*/ ++ ++void ++print_text(line) ++ char *line; ++{ ++ if(!ps_global->print->err && fputs(line, ps_global->print->fp) == EOF) ++ ps_global->print->err = 1; ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ printf style formatting with one arg for printer ++ ++ Args: line -- The printf control string ++ a1 -- The 1st argument for printf ++ ----*/ ++void ++print_text1(line, a1) ++ char *line, *a1; ++{ ++ if(!ps_global->print->err ++ && fprintf(ps_global->print->fp, line, a1) < 0) ++ ps_global->print->err = 1; ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ printf style formatting with one arg for printer ++ ++ Args: line -- The printf control string ++ a1 -- The 1st argument for printf ++ a2 -- The 2nd argument for printf ++ ----*/ ++void ++print_text2(line, a1, a2) ++ char *line, *a1, *a2; ++{ ++ if(!ps_global->print->err ++ && fprintf(ps_global->print->fp, line, a1, a2) < 0) ++ ps_global->print->err = 1; ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ printf style formatting with one arg for printer ++ ++ Args: line -- The printf control string ++ a1 -- The 1st argument for printf ++ a2 -- The 2nd argument for printf ++ a3 -- The 3rd argument for printf ++ ----*/ ++void ++print_text3(line, a1, a2, a3) ++ char *line, *a1, *a2, *a3; ++{ ++ if(!ps_global->print->err ++ && fprintf(ps_global->print->fp, line, a1, a2, a3) < 0) ++ ps_global->print->err = 1; ++} ++ ++#ifdef DEBUG ++/*---------------------------------------------------------------------- ++ Initialize debugging - open the debug log file ++ ++ Args: none ++ ++ Result: opens the debug logfile for dprints ++ ++ Opens the file "~/.pine-debug1. Also maintains .pine-debug[2-4] ++ by renaming them each time so the last 4 sessions are saved. ++ ----*/ ++void ++init_debug() ++{ ++ char nbuf[5]; ++ char newfname[MAXPATH+1], filename[MAXPATH+1]; ++ int i, fd; ++ ++ if(!(debug || ps_global->debug_imap)) ++ return; ++ ++ for(i = ps_global->debug_nfiles - 1; i > 0; i--){ ++ build_path(filename, ps_global->home_dir, DEBUGFILE); ++ strcpy(newfname, filename); ++ sprintf(nbuf, "%d", i); ++ strcat(filename, nbuf); ++ sprintf(nbuf, "%d", i+1); ++ strcat(newfname, nbuf); ++ (void)rename_file(filename, newfname); ++ } ++ ++ build_path(filename, ps_global->home_dir, DEBUGFILE); ++ strcat(filename, "1"); ++ ++ debugfile = NULL; ++ if((fd = open(filename, O_TRUNC|O_RDWR|O_CREAT, 0600)) >= 0) ++ debugfile = fdopen(fd, "w+"); ++ ++ if(debugfile != NULL){ ++ time_t now = time((time_t *)0); ++ if(ps_global->debug_flush) ++ setbuf(debugfile, NULL); ++ ++ if(ps_global->debug_nfiles == 0){ ++ /* ++ * If no debug files are asked for, make filename a tempfile ++ * to be used for a record should pine later crash... ++ */ ++ if(debug < 9 && !ps_global->debug_flush && ps_global->debug_imap<4) ++ unlink(filename); ++ } ++ ++ dprint(0, (debugfile, ++ "Debug output of the Pine program (debug=%d debug_imap=%d). Version %s\n%s\n", ++ debug, ps_global->debug_imap, pine_version, ctime(&now))); ++ } ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Try to save the debug file if we crash in a controlled way ++ ++ Args: dfile: pointer to open debug file ++ ++ Result: tries to move the appropriate .pine-debugx file to .pine-crash ++ ++ Looks through the four .pine-debug files hunting for the one that is ++ associated with this pine, and then renames it. ++ ----*/ ++void ++save_debug_on_crash(dfile) ++FILE *dfile; ++{ ++ char nbuf[5], crashfile[MAXPATH+1], filename[MAXPATH+1]; ++ int i; ++ struct stat dbuf, tbuf; ++ time_t now = time((time_t *)0); ++ ++ if(!(dfile && fstat(fileno(dfile), &dbuf) != 0)) ++ return; ++ ++ fprintf(dfile, "\nsave_debug_on_crash: Version %s: debug level %d\n", ++ pine_version, debug); ++ fprintf(dfile, "\n : %s\n", ctime(&now)); ++ ++ build_path(crashfile, ps_global->home_dir, ".pine-crash"); ++ ++ fprintf(dfile, "\nAttempting to save debug file to %s\n", crashfile); ++ fprintf(stderr, ++ "\n\n Attempting to save debug file to %s\n\n", crashfile); ++ ++ /* Blat out last n keystrokes */ ++ fputs("========== Latest keystrokes ==========\n", dfile); ++ while((i = key_playback(0)) != -1) ++ fprintf(dfile, "\t%s\t(0x%04.4x)\n", pretty_command(i), i); ++ ++ /* look for existing debug file */ ++ for(i = 1; i <= ps_global->debug_nfiles; i++){ ++ build_path(filename, ps_global->home_dir, DEBUGFILE); ++ sprintf(nbuf, "%d", i); ++ strcat(filename, nbuf); ++ if(stat(filename, &tbuf) != 0) ++ continue; ++ ++ /* This must be the current debug file */ ++ if(tbuf.st_dev == dbuf.st_dev && tbuf.st_ino == dbuf.st_ino){ ++ rename_file(filename, crashfile); ++ break; ++ } ++ } ++ ++ /* if current debug file name not found, write it by hand */ ++ if(i > ps_global->debug_nfiles){ ++ FILE *cfp; ++ char buf[1025]; ++ ++ /* ++ * Copy the debug temp file into the ++ */ ++ if(cfp = fopen(crashfile, "w")){ ++ buf[1024] = '\0'; ++ fseek(dfile, 0L, 0); ++ while(fgets(buf, 1025, dfile) && fputs(buf, cfp) != EOF) ++ ; ++ ++ fclose(cfp); ++ } ++ } ++ ++ fclose(dfile); ++} ++ ++ ++#define CHECK_EVERY_N_TIMES 100 ++#define MAX_DEBUG_FILE_SIZE 200000L ++/* ++ * This is just to catch runaway Pines that are looping spewing out ++ * debugging (and filling up a file system). The stop doesn't have to be ++ * at all precise, just soon enough to hopefully prevent filling the ++ * file system. If the debugging level is high (9 for now), then we're ++ * presumably looking for some problem, so don't truncate. ++ */ ++int ++do_debug(debug_fp) ++FILE *debug_fp; ++{ ++ static int counter = CHECK_EVERY_N_TIMES; ++ static int ok = 1; ++ long filesize; ++ ++ if(debug == DEFAULT_DEBUG ++ && !ps_global->debug_flush ++ && !ps_global->debug_timestamp ++ && ps_global->debug_imap < 2 ++ && ok ++ && --counter <= 0){ ++ if((filesize = fp_file_size(debug_fp)) != -1L) ++ ok = (unsigned long)filesize < (unsigned long)MAX_DEBUG_FILE_SIZE; ++ ++ counter = CHECK_EVERY_N_TIMES; ++ if(!ok){ ++ fprintf(debug_fp, "\n\n --- No more debugging ---\n"); ++ fprintf(debug_fp, ++ " (debug file growing too large - over %ld bytes)\n\n", ++ MAX_DEBUG_FILE_SIZE); ++ fflush(debug_fp); ++ } ++ } ++ ++ if(ok && ps_global->debug_timestamp) ++ fprintf(debug_fp, "\n%s\n", debug_time(0)); ++ ++ return(ok); ++} ++ ++ ++/* ++ * Returns a pointer to static string for a timestamp. ++ * ++ * If timestamp is set .subseconds are added if available. ++ * If include_date is set the date is appended. ++ */ ++char * ++debug_time(include_date) ++ int include_date; ++{ ++ time_t t; ++ struct tm *tm_now; ++ struct timeval tp; ++ struct timezone tzp; ++ static char timestring[23]; ++ char subsecond[8]; ++ char datestr[7]; ++ ++ if(gettimeofday(&tp, &tzp) == 0){ ++ t = (time_t)tp.tv_sec; ++ if(include_date){ ++ tm_now = localtime(&t); ++ sprintf(datestr, " %d/%d", tm_now->tm_mon+1, tm_now->tm_mday); ++ } ++ else ++ datestr[0] = '\0'; ++ ++ if(ps_global->debug_timestamp) ++ sprintf(subsecond, ".%06ld", tp.tv_usec); ++ else ++ subsecond[0] = '\0'; ++ ++ sprintf(timestring, "%.8s%s%s", ctime(&t)+11, subsecond, datestr); ++ } ++ else ++ timestring[0] = '\0'; ++ ++ return(timestring); ++} ++#endif /* DEBUG */ ++ ++ ++/* ++ * Fills in the passed in structure with the current time. ++ * ++ * Returns 0 if ok ++ * -1 if can't do it ++ */ ++int ++get_time(our_time_val) ++ TIMEVAL_S *our_time_val; ++{ ++ struct timeval tp; ++ struct timezone tzp; ++ ++ if(gettimeofday(&tp, &tzp) == 0){ ++ our_time_val->sec = tp.tv_sec; ++ our_time_val->usec = tp.tv_usec; ++ return 0; ++ } ++ else ++ return -1; ++} ++ ++ ++/* ++ * Returns the difference between the two values, in microseconds. ++ * Value returned is first - second. ++ */ ++long ++time_diff(first, second) ++ TIMEVAL_S *first, ++ *second; ++{ ++ return(1000000L*(first->sec - second->sec) + (first->usec - second->usec)); ++} ++ ++ ++ ++/*====================================================================== ++ 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 ++ */ ++#define RETURN_CH(X) return(key_recorder((X))) ++/* ++ * 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_recorder, read_bail) ++ ++ ++/* ++ * Internal prototypes ++ */ ++void line_paint PROTO((int, int *)); ++int process_config_input PROTO((int *)); ++int check_for_timeout PROTO((int)); ++void read_bail PROTO((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. ++ ----------------------------------------------------------------------*/ ++init_tty_driver(ps) ++ 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(ps, mode, func) ++ 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(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(ps) ++ struct pine *ps; ++{ ++ ps = ps; /* get rid of unused parameter warning */ ++ ++#ifdef MOUSE ++ end_mouse(); ++#endif /* MOUSE */ ++ fflush(stdout); ++ dprint(2, (debugfile, "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. ++ ----*/ ++ ++PineRaw(state) ++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 thr trouble of ++ * getting it right ++ */ ++ if(ps_global->VAR_CHAR_SET && ps_global->VAR_CHAR_SET[0] && ++ strucmp(ps_global->VAR_CHAR_SET, "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); ++} ++ ++ ++#ifdef RESIZING ++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. ++ ----*/ ++int ++check_for_timeout(time_out) ++ int time_out; ++{ ++ int res; ++ ++ fflush(stdout); ++ ++#ifdef RESIZING ++ if(winch_occured || setjmp(winch_state) != 0){ ++ ready_for_winch = 0; ++ fix_windsize(ps_global); ++ ++ /* ++ * May need to unblock signal after longjmp from handler, because ++ * signal is normally unblocked upon routine exit from the handler. ++ */ ++ if(!winch_occured) ++ our_sigunblock(SIGWINCH); ++ ++ winch_occured = 0; ++ return(KEY_RESIZE); ++ } ++ else ++ ready_for_winch = 1; ++#endif /* RESIZING */ ++ ++ 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: ++#ifdef RESIZING ++ ready_for_winch = 0; ++#endif ++ 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. ++ ---*/ ++int ++read_char(time_out) ++ int time_out; ++{ ++ int ch, status, cc; ++ ++ /* Get input from initial-keystrokes */ ++ if(process_config_input(&ch)) ++ return(ch); ++ ++ /* ++ * We only check for timeouts at the start of read_char, not in the ++ * middle of escape sequences. ++ */ ++ if((ch = check_for_timeout(time_out)) != READY_TO_READ) ++ goto done; ++ ++ ps_global->time_of_last_input = time((time_t *)0); ++ switch(status = kbseq(simple_ttgetc, key_recorder, read_bail, &ch)){ ++ case KEY_DOUBLE_ESC: ++ /* ++ * Special hack to get around comm devices eating control characters. ++ */ ++ if(check_for_timeout(5) != READY_TO_READ){ ++ ch = KEY_JUNK; /* user typed ESC ESC, then stopped */ ++ goto done; ++ } ++ else ++ ch = READ_A_CHAR(); ++ ++ ch &= 0x7f; ++ if(isdigit((unsigned char)ch)){ ++ int n = 0, i = ch - '0'; ++ ++ if(i < 0 || i > 2){ ++ 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'))){ ++ ch = KEY_JUNK; /* user typed ESC ESC #, stopped */ ++ goto done; ++ } ++ ++ i = (i * 10) + (ch - '0'); ++ } ++ ++ ch = i; ++ } ++ else{ ++ 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); ++ } ++ ++ 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 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 = (int)cmd; ++ } ++ else if (down && button == 3){ ++ down = 0; ++ if(checkmouse(&cmd, 0, x, y)) ++ ch = (int)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, (debugfile, "Read char returning: %d %s\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 & 0x80) && (!ps_global->VAR_CHAR_SET ++ || !strucmp(ps_global->VAR_CHAR_SET, "US-ASCII"))){ ++ dprint(9, (debugfile, "Read char returning: %d %s\n", ++ status, pretty_command(status))); ++ return(KEY_JUNK); ++ } ++ else if(ch == ctrl('Z')){ ++ dprint(9, (debugfile, "Read char calling do_suspend\n")); ++ return(do_suspend()); ++ } ++ ++ ++ done: ++ dprint(9, (debugfile, "Read char returning: %d %s\n", ++ ch, pretty_command(ch))); ++ return(ch); ++ } ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Reading input somehow failed and we need to shutdown now ++ ++ Args: none ++ ++ Result: pine exits ++ ++ ---*/ ++void ++read_bail() ++{ ++ end_signals(1); ++ if(ps_global->inbox_stream){ ++ if(ps_global->inbox_stream == ps_global->mail_stream) ++ ps_global->mail_stream = NULL; ++ ++ if(!ps_global->inbox_stream->lock) /* shouldn't be... */ ++ pine_close_stream(ps_global->inbox_stream); ++ } ++ ++ if(ps_global->mail_stream && !ps_global->mail_stream->lock) ++ pine_close_stream(ps_global->mail_stream); ++ ++ end_keyboard(F_ON(F_USE_FK,ps_global)); ++ end_tty_driver(ps_global); ++ if(filter_data_file(0)) ++ unlink(filter_data_file(0)); ++ ++ exit(0); ++} ++ ++ ++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(use_fkeys) ++ 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(use_fkeys) ++ int use_fkeys; ++{ ++ if(use_fkeys && (!strcmp(term_name, "vt102") ++ || !strcmp(term_name, "vt100"))){ ++ printf("\033\133\071\071\154"); ++ fflush(stdout); ++ } ++} ++ ++ ++#ifdef _WINDOWS ++#line 3 "osdep/termin.gen" ++#endif ++/* ++ * Generic tty input routines ++ */ ++ ++ ++/*---------------------------------------------------------------------- ++ Read a character from keyboard with timeout ++ Input: none ++ ++ Result: Returns command read via read_char ++ Times out and returns a null command every so often ++ ++ Calculates the timeout for the read, and does a few other house keeping ++things. The duration of the timeout is set in pine.c. ++ ----------------------------------------------------------------------*/ ++int ++read_command() ++{ ++ int ch, tm = 0; ++ long dtime; ++ ++ cancel_busy_alarm(-1); ++ tm = (messages_queued(&dtime) > 1) ? (int)dtime : timeo; ++ ++ /* ++ * Before we sniff at the input queue, make sure no external event's ++ * changed our picture of the message sequence mapping. If so, ++ * recalculate the dang thing and run thru whatever processing loop ++ * we're in again... ++ */ ++ if(ps_global->expunge_count){ ++ q_status_message2(SM_ORDER, 3, 3, ++ "自資料匣 \"%s\" 中刪除 %s 封信件", ++ pretty_fn(ps_global->cur_folder), ++ long2string(ps_global->expunge_count)); ++ ps_global->expunge_count = 0L; ++ display_message('x'); ++ } ++ ++ if(ps_global->inbox_expunge_count){ ++ q_status_message2(SM_ORDER, 3, 3, ++ "自資料匣 \"%s\" 中刪除 %s 封信件", ++ pretty_fn(ps_global->inbox_name), ++ long2string(ps_global->inbox_expunge_count)); ++ ps_global->inbox_expunge_count = 0L; ++ display_message('x'); ++ } ++ ++ if(ps_global->mail_box_changed && ps_global->new_mail_count){ ++ dprint(2, (debugfile, "Noticed %ld new msgs! \n", ++ ps_global->new_mail_count)); ++ return(NO_OP_COMMAND); /* cycle thru so caller can update */ ++ } ++ ++ ch = read_char(tm); ++ dprint(9, (debugfile, "Read command returning: %d %s\n", ch, ++ pretty_command(ch))); ++ if(ch != NO_OP_COMMAND && ch != NO_OP_IDLE && ch != KEY_RESIZE) ++ zero_new_mail_count(); ++ ++#ifdef BACKGROUND_POST ++ /* ++ * Any expired children to report on? ++ */ ++ if(ps_global->post && ps_global->post->pid == 0){ ++ int winner = 0; ++ ++ if(ps_global->post->status < 0){ ++ q_status_message(SM_ORDER | SM_DING, 3, 3, "徹底失敗!"); ++ } ++ else{ ++ (void) pine_send_status(ps_global->post->status, ++ ps_global->post->fcc, tmp_20k_buf, ++ &winner); ++ q_status_message(SM_ORDER | (winner ? 0 : SM_DING), 3, 3, ++ tmp_20k_buf); ++ ++ } ++ ++ if(!winner) ++ q_status_message(SM_ORDER, 0, 3, ++ "由 \"編修\" 再回答 \"是\" 來繼續重送 \"上次中斷的信件?\""); ++/* ++ "Re-send via \"Compose\" then \"Yes\" to \"Continue INTERRUPTED?\""); ++*/ ++ if(ps_global->post->fcc) ++ fs_give((void **) &ps_global->post->fcc); ++ ++ fs_give((void **) &ps_global->post); ++ } ++#endif ++ ++ return(ch); ++} ++ ++ ++ ++ ++/* ++ * ++ */ ++static struct display_line { ++ int row, col; /* where display starts */ ++ int dlen; /* length of display line */ ++ char *dl; /* line on display */ ++ char *vl; /* virtual line */ ++ int vlen; /* length of virtual line */ ++ int vused; /* length of virtual line in use */ ++ int vbase; /* first virtual char on display */ ++} dline; ++ ++ ++ ++static struct key oe_keys[] = ++ {{"^G","輔助說明",KS_SCREENHELP}, {"^C","取消",KS_NONE}, ++ {"^T","xxx",KS_NONE}, {"Ret","同意",KS_NONE}, ++ {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}, ++ {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}, ++ {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}, ++ {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}}; ++INST_KEY_MENU(oe_keymenu, oe_keys); ++#define OE_HELP_KEY 0 ++#define OE_CANCEL_KEY 1 ++#define OE_CTRL_T_KEY 2 ++#define OE_ENTER_KEY 3 ++ ++ ++/*---------------------------------------------------------------------- ++ Prompt user for a string in status line with various options ++ ++ Args: string -- the buffer result is returned in, and original string (if ++ any) is passed in. ++ y_base -- y position on screen to start on. 0,0 is upper left ++ negative numbers start from bottom ++ x_base -- column position on screen to start on. 0,0 is upper left ++ field_len -- Maximum length of string to accept ++ prompt -- The string to prompt with ++ escape_list -- pointer to array of ESCKEY_S's. input chars matching ++ those in list return value from list. ++ help -- Arrary of strings for help text in bottom screen lines ++ flags -- pointer (because some are return values) to flags ++ OE_USER_MODIFIED - Set on return if user modified buffer ++ OE_DISALLOW_CANCEL - No cancel in menu. ++ OE_DISALLOW_HELP - No help in menu. ++ OE_KEEP_TRAILING_SPACE - Allow trailing space. ++ OE_SEQ_SENSITIVE - Caller is sensitive to sequence ++ number changes. ++ OE_APPEND_CURRENT - String should not be truncated ++ before accepting user input. ++ OE_PASSWD - Don't echo on screen. ++ ++ Result: editing input string ++ returns -1 unexpected errors ++ returns 0 normal entry typed (editing and return or PF2) ++ returns 1 typed ^C or PF2 (cancel) ++ returns 3 typed ^G or PF1 (help) ++ returns 4 typed ^L for a screen redraw ++ ++ WARNING: Care is required with regard to the escape_list processing. ++ The passed array is terminated with an entry that has ch = -1. ++ Function key labels and key strokes need to be setup externally! ++ Traditionally, a return value of 2 is used for ^T escapes. ++ ++ Unless in escape_list, tabs are trapped by isprint(). ++This allows near full weemacs style editing in the line ++ ^A beginning of line ++ ^E End of line ++ ^R Redraw line ++ ^G Help ++ ^F forward ++ ^B backward ++ ^D delete ++----------------------------------------------------------------------*/ ++ ++optionally_enter(string, y_base, x_base, field_len, ++ prompt, escape_list, help, flags) ++ char *string, *prompt; ++ ESCKEY_S *escape_list; ++ HelpType help; ++ int x_base, y_base, field_len; ++ int *flags; ++{ ++ register char *s2; ++ register int field_pos; ++ int i, j, return_v, cols, ch, prompt_len, too_thin, ++ real_y_base, km_popped, passwd; ++ char *saved_original = NULL, *k, *kb; ++ char *kill_buffer = NULL; ++ char **help_text; ++ int fkey_table[12]; ++ struct key_menu *km; ++ bitmap_t bitmap; ++#ifdef _WINDOWS ++ int cursor_shown; ++#endif ++ ++ dprint(5, (debugfile, "=== optionally_enter called ===\n")); ++ dprint(9, (debugfile, "string:\"%s\" y:%d x:%d length: %d append: %d\n", ++ string, x_base, y_base, field_len, ++ (flags && *flags & OE_APPEND_CURRENT))); ++ dprint(9, (debugfile, "passwd:%d prompt:\"%s\" label:\"%s\"\n", ++ (flags && *flags & OE_PASSWD), ++ prompt, (escape_list && escape_list[0].ch != -1) ++ ? escape_list[0].label: "")); ++ ++#ifdef _WINDOWS ++ if (mswin_usedialog ()) { ++ MDlgButton button_list[12]; ++ int b; ++ int i; ++ ++ memset (&button_list, 0, sizeof (MDlgButton) * 12); ++ b = 0; ++ for (i = 0; escape_list && escape_list[i].ch != -1 && i < 11; ++i) { ++ if (escape_list[i].name != NULL ++ && escape_list[i].ch > 0 && escape_list[i].ch < 256) { ++ button_list[b].ch = escape_list[i].ch; ++ button_list[b].rval = escape_list[i].rval; ++ button_list[b].name = escape_list[i].name; ++ button_list[b].label = escape_list[i].label; ++ ++b; ++ } ++ } ++ button_list[b].ch = -1; ++ ++ ++ help_text = get_help_text (help); ++ return_v = mswin_dialog (prompt, string, field_len, ++ (flags && *flags & OE_APPEND_CURRENT), ++ (flags && *flags & OE_PASSWD), ++ button_list, ++ help_text, flags ? *flags : OE_NONE); ++ free_list_array (&help_text); ++ return (return_v); ++ } ++#endif ++ ++ suspend_busy_alarm(); ++ cols = ps_global->ttyo->screen_cols; ++ prompt_len = strlen(prompt); ++ too_thin = 0; ++ km_popped = 0; ++ if(y_base > 0) { ++ real_y_base = y_base; ++ } else { ++ real_y_base= y_base + ps_global->ttyo->screen_rows; ++ if(real_y_base < 2) ++ real_y_base = ps_global->ttyo->screen_rows; ++ } ++ ++ flush_ordered_messages(); ++ mark_status_dirty(); ++ if(flags && *flags & OE_APPEND_CURRENT) /* save a copy in case of cancel */ ++ saved_original = cpystr(string); ++ ++ /* ++ * build the function key mapping table, skipping predefined keys... ++ */ ++ memset(fkey_table, NO_OP_COMMAND, 12 * sizeof(int)); ++ for(i = 0, j = 0; escape_list && escape_list[i].ch != -1 && i+j < 12; i++){ ++ if(i+j == OE_HELP_KEY) ++ j++; ++ ++ if(i+j == OE_CANCEL_KEY) ++ j++; ++ ++ if(i+j == OE_ENTER_KEY) ++ j++; ++ ++ fkey_table[i+j] = escape_list[i].ch; ++ } ++ ++#if defined(HELPFILE) ++ help_text = (help != NO_HELP) ? get_help_text(help) : (char **)NULL; ++#else ++ help_text = help; ++#endif ++ if(help_text){ /*---- Show help text -----*/ ++ int width = ps_global->ttyo->screen_cols - x_base; ++ ++ if(FOOTER_ROWS(ps_global) == 1){ ++ km_popped++; ++ FOOTER_ROWS(ps_global) = 3; ++ clearfooter(ps_global); ++ ++ y_base = -3; ++ real_y_base = y_base + ps_global->ttyo->screen_rows; ++ } ++ ++ for(j = 0; j < 2 && help_text[j]; j++){ ++ MoveCursor(real_y_base + 1 + j, x_base); ++ CleartoEOLN(); ++ ++ if(width < strlen(help_text[j])){ ++ char *tmp = fs_get((width + 1) * sizeof(char)); ++ strncpy(tmp, help_text[j], width); ++ tmp[width] = '\0'; ++ PutLine0(real_y_base + 1 + j, x_base, tmp); ++ fs_give((void **)&tmp); ++ } ++ else ++ PutLine0(real_y_base + 1 + j, x_base, help_text[j]); ++ } ++ ++#if defined(HELPFILE) ++ free_list_array(&help_text); ++#endif ++ ++ } else { ++ clrbitmap(bitmap); ++ clrbitmap((km = &oe_keymenu)->bitmap); /* force formatting */ ++ if(!(flags && (*flags) & OE_DISALLOW_HELP)) ++ setbitn(OE_HELP_KEY, bitmap); ++ ++ setbitn(OE_ENTER_KEY, bitmap); ++ if(!(flags && (*flags) & OE_DISALLOW_CANCEL)) ++ setbitn(OE_CANCEL_KEY, bitmap); ++ ++ setbitn(OE_CTRL_T_KEY, bitmap); ++ ++ /*---- Show the usual possible keys ----*/ ++ for(i=0,j=0; escape_list && escape_list[i].ch != -1 && i+j < 12; i++){ ++ if(i+j == OE_HELP_KEY) ++ j++; ++ ++ if(i+j == OE_CANCEL_KEY) ++ j++; ++ ++ if(i+j == OE_ENTER_KEY) ++ j++; ++ ++ oe_keymenu.keys[i+j].label = escape_list[i].label; ++ oe_keymenu.keys[i+j].name = escape_list[i].name; ++ setbitn(i+j, bitmap); ++ } ++ ++ for(i = i+j; i < 12; i++) ++ if(!(i == OE_HELP_KEY || i == OE_ENTER_KEY || i == OE_CANCEL_KEY)) ++ oe_keymenu.keys[i].name = NULL; ++ ++ draw_keymenu(km, bitmap, cols, 1-FOOTER_ROWS(ps_global), 0, FirstMenu); ++ } ++ ++ StartInverse(); /* Always in inverse */ ++ ++ /* ++ * if display length isn't wide enough to support input, ++ * shorten up the prompt... ++ */ ++ if((dline.dlen = cols - (x_base + prompt_len + 1)) < 5){ ++ prompt_len += (dline.dlen - 5); /* adding negative numbers */ ++ prompt -= (dline.dlen - 5); /* subtracting negative numbers */ ++ dline.dlen = 5; ++ } ++ ++ dline.dl = fs_get((size_t)dline.dlen + 1); ++ memset((void *)dline.dl, 0, (size_t)(dline.dlen + 1) * sizeof(char)); ++ dline.row = real_y_base; ++ dline.col = x_base + prompt_len; ++ dline.vl = string; ++ dline.vlen = --field_len; /* -1 for terminating NULL */ ++ dline.vbase = field_pos = 0; ++ ++#ifdef _WINDOWS ++ cursor_shown = mswin_showcursor(1); ++#endif ++ ++ PutLine0(real_y_base, x_base, prompt); ++ /* make sure passed in string is shorter than field_len */ ++ /* and adjust field_pos.. */ ++ ++ while((flags && *flags & OE_APPEND_CURRENT) && ++ field_pos < field_len && string[field_pos] != '\0') ++ field_pos++; ++ ++ string[field_pos] = '\0'; ++ dline.vused = (int)(&string[field_pos] - string); ++ passwd = (flags && *flags & OE_PASSWD) ? 1 : 0; ++ line_paint(field_pos, &passwd); ++ ++ /*---------------------------------------------------------------------- ++ The main loop ++ ++ here field_pos is the position in the string. ++ s always points to where we are in the string. ++ loops until someone sets the return_v. ++ ----------------------------------------------------------------------*/ ++ return_v = -10; ++ ++#ifdef _WINDOWS ++ mswin_allowpaste(MSWIN_PASTE_LINE); ++#endif ++ ++ while(return_v == -10) { ++ /* Timeout 10 min to keep imap mail stream alive */ ++ ch = read_char(600); ++ ++ /* ++ * Don't want to intercept all characters if typing in passwd. ++ * We select an ad hoc set that we will catch and let the rest ++ * through. We would have caught the set below in the big switch ++ * but we skip the switch instead. Still catch things like ^K, ++ * DELETE, ^C, RETURN. ++ */ ++ if(passwd) ++ switch(ch) { ++ case ctrl('F'): ++ case KEY_RIGHT: ++ case ctrl('B'): ++ case KEY_LEFT: ++ case ctrl('U'): ++ case ctrl('A'): ++ case KEY_HOME: ++ case ctrl('E'): ++ case KEY_END: ++ case TAB: ++ goto ok_for_passwd; ++ } ++ ++ if(too_thin && ch != KEY_RESIZE && ch != ctrl('Z') && ch != ctrl('C')) ++ goto bleep; ++ ++ switch(ch) { ++ ++ /*--------------- KEY RIGHT ---------------*/ ++ case ctrl('F'): ++ case KEY_RIGHT: ++ if(field_pos >= field_len || string[field_pos] == '\0') ++ goto bleep; ++ ++ line_paint(++field_pos, &passwd); ++ break; ++ ++ /*--------------- KEY LEFT ---------------*/ ++ case ctrl('B'): ++ case KEY_LEFT: ++ if(field_pos <= 0) ++ goto bleep; ++ ++ line_paint(--field_pos, &passwd); ++ break; ++ ++ /*-------------------- WORD SKIP --------------------*/ ++ case ctrl('@'): ++ /* ++ * Note: read_char *can* return NO_OP_COMMAND which is ++ * the def'd with the same value as ^@ (NULL), BUT since ++ * read_char has a big timeout (>25 secs) it won't. ++ */ ++ ++ /* skip thru current word */ ++ while(string[field_pos] ++ && isalnum((unsigned char) string[field_pos])) ++ field_pos++; ++ ++ /* skip thru current white space to next word */ ++ while(string[field_pos] ++ && !isalnum((unsigned char) string[field_pos])) ++ field_pos++; ++ ++ line_paint(field_pos, &passwd); ++ break; ++ ++ /*-------------------- RETURN --------------------*/ ++ case PF4: ++ if(F_OFF(F_USE_FK,ps_global)) goto bleep; ++ case ctrl('J'): ++ case ctrl('M'): ++ return_v = 0; ++ break; ++ ++ /*-------------------- Destructive backspace --------------------*/ ++ case '\177': /* DEL */ ++ case ctrl('H'): ++ /* Try and do this with by telling the terminal to delete a ++ a character. If that fails, then repaint the rest of the ++ line, acheiving the same much less efficiently ++ */ ++ if(field_pos <= 0) ++ goto bleep; ++ ++ field_pos--; ++ /* drop thru to pull line back ... */ ++ ++ /*-------------------- Delete char --------------------*/ ++ case ctrl('D'): ++ case KEY_DEL: ++ if(field_pos >= field_len || !string[field_pos]) ++ goto bleep; ++ ++ dline.vused--; ++ for(s2 = &string[field_pos]; *s2 != '\0'; s2++) ++ *s2 = s2[1]; ++ ++ *s2 = '\0'; /* Copy last NULL */ ++ line_paint(field_pos, &passwd); ++ if(flags) /* record change if requested */ ++ *flags |= OE_USER_MODIFIED; ++ ++ break; ++ ++ ++ /*--------------- Kill line -----------------*/ ++ case ctrl('K'): ++ if(kill_buffer != NULL) ++ fs_give((void **)&kill_buffer); ++ ++ if(field_pos != 0 || string[0]){ ++ if(!passwd && F_ON(F_DEL_FROM_DOT, ps_global)) ++ dline.vused -= strlen(&string[i = field_pos]); ++ else ++ dline.vused = i = 0; ++ ++ kill_buffer = cpystr(&string[field_pos = i]); ++ string[field_pos] = '\0'; ++ line_paint(field_pos, &passwd); ++ if(flags) /* record change if requested */ ++ *flags |= OE_USER_MODIFIED; ++ ++ } ++ ++ break; ++ ++ /*------------------- Undelete line --------------------*/ ++ case ctrl('U'): ++ if(kill_buffer == NULL) ++ goto bleep; ++ ++ /* Make string so it will fit */ ++ kb = cpystr(kill_buffer); ++ dprint(2, (debugfile, ++ "Undelete: %d %d\n", strlen(string), field_len)); ++ if(strlen(kb) + strlen(string) > field_len) ++ kb[field_len - strlen(string)] = '\0'; ++ dprint(2, (debugfile, ++ "Undelete: %d %d\n", field_len - strlen(string), ++ strlen(kb))); ++ ++ if(string[field_pos] == '\0') { ++ /*--- adding to the end of the string ----*/ ++ for(k = kb; *k; k++) ++ string[field_pos++] = *k; ++ ++ string[field_pos] = '\0'; ++ } else { ++ goto bleep; ++ /* To lazy to do insert in middle of string now */ ++ } ++ ++ if(*kb && flags) /* record change if requested */ ++ *flags |= OE_USER_MODIFIED; ++ ++ dline.vused = strlen(string); ++ fs_give((void **)&kb); ++ line_paint(field_pos, &passwd); ++ break; ++ ++ ++ /*-------------------- Interrupt --------------------*/ ++ case ctrl('C'): /* ^C */ ++ if(F_ON(F_USE_FK,ps_global) ++ || (flags && ((*flags) & OE_DISALLOW_CANCEL))) ++ goto bleep; ++ ++ goto cancel; ++ ++ case PF2: ++ if(F_OFF(F_USE_FK,ps_global) ++ || (flags && ((*flags) & OE_DISALLOW_CANCEL))) ++ goto bleep; ++ ++ cancel: ++ return_v = 1; ++ if(saved_original) ++ strcpy(string, saved_original); ++ ++ break; ++ ++ ++ case ctrl('A'): ++ case KEY_HOME: ++ /*-------------------- Start of line -------------*/ ++ line_paint(field_pos = 0, &passwd); ++ break; ++ ++ ++ case ctrl('E'): ++ case KEY_END: ++ /*-------------------- End of line ---------------*/ ++ line_paint(field_pos = dline.vused, &passwd); ++ break; ++ ++ ++ /*-------------------- Help --------------------*/ ++ case ctrl('G') : ++ case PF1: ++ if(flags && ((*flags) & OE_DISALLOW_HELP)) ++ goto bleep; ++ else if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){ ++ km_popped++; ++ FOOTER_ROWS(ps_global) = 3; ++ clearfooter(ps_global); ++ EndInverse(); ++ draw_keymenu(km, bitmap, cols, 1-FOOTER_ROWS(ps_global), ++ 0, FirstMenu); ++ StartInverse(); ++ mark_keymenu_dirty(); ++ y_base = -3; ++ dline.row = real_y_base = y_base + ps_global->ttyo->screen_rows; ++ PutLine0(real_y_base, x_base, prompt); ++ fs_resize((void **)&dline.dl, (size_t)dline.dlen + 1); ++ memset((void *)dline.dl, 0, (size_t)(dline.dlen + 1)); ++ line_paint(field_pos, &passwd); ++ break; ++ } ++ ++ if(FOOTER_ROWS(ps_global) > 1){ ++ mark_keymenu_dirty(); ++ return_v = 3; ++ } ++ else ++ goto bleep; ++ ++ break; ++ ++ case NO_OP_IDLE: ++ /* Keep mail stream alive */ ++ i = new_mail(0, 2, NM_DEFER_SORT); ++ if(ps_global->expunge_count && ++ flags && ((*flags) & OE_SEQ_SENSITIVE)) ++ goto cancel; ++ ++ if(i < 0) ++ break; /* no changes, get on with life */ ++ /* Else fall into redraw */ ++ ++ /*-------------------- Redraw --------------------*/ ++ case ctrl('L'): ++ /*---------------- re size ----------------*/ ++ case KEY_RESIZE: ++ ++ dline.row = real_y_base = y_base > 0 ? y_base : ++ y_base + ps_global->ttyo->screen_rows; ++ EndInverse(); ++ ClearScreen(); ++ redraw_titlebar(); ++ if(ps_global->redrawer != (void (*)())NULL) ++ (*ps_global->redrawer)(); ++ ++ redraw_keymenu(); ++ StartInverse(); ++ ++ PutLine0(real_y_base, x_base, prompt); ++ cols = ps_global->ttyo->screen_cols; ++ too_thin = 0; ++ if(cols < x_base + prompt_len + 4) { ++ Writechar(BELL, 0); ++ PutLine0(real_y_base, 0, "Screen's too thin. Ouch!"); ++ too_thin = 1; ++ } else { ++ dline.col = x_base + prompt_len; ++ dline.dlen = cols - (x_base + prompt_len + 1); ++ fs_resize((void **)&dline.dl, (size_t)dline.dlen + 1); ++ memset((void *)dline.dl, 0, (size_t)(dline.dlen + 1)); ++ line_paint(field_pos, &passwd); ++ } ++ fflush(stdout); ++ ++ dprint(9, (debugfile, ++ "optionally_enter RESIZE new_cols:%d too_thin: %d\n", ++ cols, too_thin)); ++ break; ++ ++ case PF3 : /* input to potentially remap */ ++ case PF5 : ++ case PF6 : ++ case PF7 : ++ case PF8 : ++ case PF9 : ++ case PF10 : ++ case PF11 : ++ case PF12 : ++ if(F_ON(F_USE_FK,ps_global) ++ && fkey_table[ch - PF1] != NO_OP_COMMAND) ++ ch = fkey_table[ch - PF1]; /* remap function key input */ ++ ++ default: ++ if(escape_list){ /* in the escape key list? */ ++ for(j=0; escape_list[j].ch != -1; j++){ ++ if(escape_list[j].ch == ch){ ++ return_v = escape_list[j].rval; ++ break; ++ } ++ } ++ ++ if(return_v != -10) ++ break; ++ } ++ ++ if(iscntrl(ch & 0x7f)){ ++ bleep: ++ putc(BELL, stdout); ++ continue; ++ } ++ ++ ok_for_passwd: ++ /*--- Insert a character -----*/ ++ if(dline.vused >= field_len) ++ goto bleep; ++ ++ /*---- extending the length of the string ---*/ ++ for(s2 = &string[++dline.vused]; s2 - string > field_pos; s2--) ++ *s2 = *(s2-1); ++ string[field_pos++] = ch; ++ line_paint(field_pos, &passwd); ++ if(flags) /* record change if requested */ ++ *flags |= OE_USER_MODIFIED; ++ ++ } /*---- End of switch on char ----*/ ++ } ++ ++#ifdef _WINDOWS ++ if(!cursor_shown) ++ mswin_showcursor(0); ++ ++ mswin_allowpaste(MSWIN_PASTE_DISABLE); ++#endif ++ fs_give((void **)&dline.dl); ++ if(saved_original) ++ fs_give((void **)&saved_original); ++ ++ if(kill_buffer) ++ fs_give((void **)&kill_buffer); ++ ++ if (!(flags && (*flags) & OE_KEEP_TRAILING_SPACE)) ++ removing_trailing_white_space(string); ++ EndInverse(); ++ MoveCursor(real_y_base, x_base); /* Move the cursor to show we're done */ ++ fflush(stdout); ++ resume_busy_alarm(0); ++ if(km_popped){ ++ FOOTER_ROWS(ps_global) = 1; ++ clearfooter(ps_global); ++ ps_global->mangled_body = 1; ++ } ++ ++ return(return_v); ++} ++ ++ ++/* ++ * line_paint - where the real work of managing what is displayed gets done. ++ * The passwd variable is overloaded: if non-zero, don't ++ * output anything, else only blat blank chars across line ++ * once and use this var to tell us we've already written the ++ * line. ++ */ ++void ++line_paint(offset, passwd) ++ int offset; /* current dot offset into line */ ++ int *passwd; /* flag to hide display of chars */ ++{ ++ register char *pfp, *pbp; ++ register char *vfp, *vbp; ++ int extra = 0; ++#define DLEN (dline.vbase + dline.dlen) ++ ++ /* ++ * for now just leave line blank, but maybe do '*' for each char later ++ */ ++ if(*passwd){ ++ if(*passwd > 1) ++ return; ++ else ++ *passwd == 2; /* only blat once */ ++ ++ extra = 0; ++ MoveCursor(dline.row, dline.col); ++ while(extra++ < dline.dlen) ++ Writechar(' ', 0); ++ ++ MoveCursor(dline.row, dline.col); ++ return; ++ } ++ ++ /* adjust right margin */ ++ while(offset >= DLEN + ((dline.vused > DLEN) ? -1 : 1)) ++ dline.vbase += dline.dlen/2; ++ ++ /* adjust left margin */ ++ while(offset < dline.vbase + ((dline.vbase) ? 2 : 0)) ++ dline.vbase = max(dline.vbase - (dline.dlen/2), 0); ++ ++ if(dline.vbase){ /* off screen cue left */ ++ vfp = &dline.vl[dline.vbase+1]; ++ pfp = &dline.dl[1]; ++ if(dline.dl[0] != '<'){ ++ MoveCursor(dline.row, dline.col); ++ Writechar(dline.dl[0] = '<', 0); ++ } ++ } ++ else{ ++ vfp = dline.vl; ++ pfp = dline.dl; ++ if(dline.dl[0] == '<'){ ++ MoveCursor(dline.row, dline.col); ++ Writechar(dline.dl[0] = ' ', 0); ++ } ++ } ++ ++ if(dline.vused > DLEN){ /* off screen right... */ ++ vbp = vfp + (long)(dline.dlen-(dline.vbase ? 2 : 1)); ++ pbp = pfp + (long)(dline.dlen-(dline.vbase ? 2 : 1)); ++ if(pbp[1] != '>'){ ++ MoveCursor(dline.row, dline.col+dline.dlen); ++ Writechar(pbp[1] = '>', 0); ++ } ++ } ++ else{ ++ extra = dline.dlen - (dline.vused - dline.vbase); ++ vbp = &dline.vl[max(0, dline.vused-1)]; ++ pbp = &dline.dl[dline.dlen]; ++ if(pbp[0] == '>'){ ++ MoveCursor(dline.row, dline.col+dline.dlen); ++ Writechar(pbp[0] = ' ', 0); ++ } ++ } ++ ++ while(*pfp == *vfp && vfp < vbp) /* skip like chars */ ++ pfp++, vfp++; ++ ++ if(pfp == pbp && *pfp == *vfp){ /* nothing to paint! */ ++ MoveCursor(dline.row, dline.col + (offset - dline.vbase)); ++ return; ++ } ++ ++ /* move backward thru like characters */ ++ if(extra){ ++ while(extra >= 0 && *pbp == ' ') /* back over spaces */ ++ extra--, pbp--; ++ ++ while(extra >= 0) /* paint new ones */ ++ pbp[-(extra--)] = ' '; ++ } ++ ++ if((vbp - vfp) == (pbp - pfp)){ /* space there? */ ++ while((*pbp == *vbp) && pbp != pfp) /* skip like chars */ ++ pbp--, vbp--; ++ } ++ ++ if(pfp != pbp || *pfp != *vfp){ /* anything to paint?*/ ++ MoveCursor(dline.row, dline.col + (int)(pfp - dline.dl)); ++ ++ do ++ Writechar((unsigned char)((vfp <= vbp && *vfp) ++ ? ((*pfp = *vfp++) == TAB) ? ' ' : *pfp ++ : (*pfp = ' ')), 0); ++ while(++pfp <= pbp); ++ } ++ ++ MoveCursor(dline.row, dline.col + (offset - dline.vbase)); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Check to see if the given command is reasonably valid ++ ++ Args: ch -- the character to check ++ ++ Result: A valid command is returned, or a well know bad command is returned. ++ ++ ---*/ ++validatekeys(ch) ++ int ch; ++{ ++#ifndef _WINDOWS ++ if(F_ON(F_USE_FK,ps_global)) { ++ if(ch >= 'a' && ch <= 'z') ++ return(KEY_JUNK); ++ } else { ++ if(ch >= PF1 && ch <= PF12) ++ return(KEY_JUNK); ++ } ++#else ++ /* ++ * In windows menu items are bound to a single key command which ++ * gets inserted into the input stream as if the user had typed ++ * that key. But all the menues are bonund to alphakey commands, ++ * not PFkeys. to distinguish between a keyboard command and a ++ * menu command we insert a flag (KEY_MENU_FLAG) into the ++ * command value when setting up the bindings in ++ * configure_menu_items(). Here we strip that flag. ++ */ ++ if(F_ON(F_USE_FK,ps_global)) { ++ if(ch >= 'a' && ch <= 'z' && !(ch & KEY_MENU_FLAG)) ++ return(KEY_JUNK); ++ ch &= ~ KEY_MENU_FLAG; ++ } else { ++ ch &= ~ KEY_MENU_FLAG; ++ if(ch >= PF1 && ch <= PF12) ++ return(KEY_JUNK); ++ } ++#endif ++ ++ return(ch); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Prepend config'd commands to keyboard input ++ ++ Args: ch -- pointer to storage for returned command ++ ++ Returns: TRUE if we're passing back a useful command, FALSE otherwise ++ ++ ---*/ ++int ++process_config_input(ch) ++ int *ch; ++{ ++ static char firsttime = (char) 1; ++ ++ /* commands in config file */ ++ if(ps_global->initial_cmds && *ps_global->initial_cmds) { ++ /* ++ * There are a few commands that may require keyboard input before ++ * we enter the main command loop. That input should be interactive, ++ * not from our list of initial keystrokes. ++ */ ++ if(ps_global->dont_use_init_cmds) ++ return(0); ++ ++ *ch = *ps_global->initial_cmds++; ++ if(!*ps_global->initial_cmds && ps_global->free_initial_cmds){ ++ fs_give((void **)&(ps_global->free_initial_cmds)); ++ ps_global->initial_cmds = 0; ++ } ++ ++ return(1); ++ } ++ ++ if(firsttime) { ++ firsttime = 0; ++ if(ps_global->in_init_seq) { ++ ps_global->in_init_seq = 0; ++ ps_global->save_in_init_seq = 0; ++ clear_cursor_pos(); ++ F_SET(F_USE_FK,ps_global,ps_global->orig_use_fkeys); ++ /* draw screen */ ++ *ch = ctrl('L'); ++ return(1); ++ } ++ } ++ ++ return(0); ++} ++ ++ ++#define TAPELEN 256 ++static int tape[TAPELEN]; ++static long recorded = 0L; ++static short length = 0; ++ ++ ++/* ++ * record user keystrokes ++ * ++ * Args: ch -- the character to record ++ * ++ * Returns: character recorded ++ */ ++int ++key_recorder(ch) ++ int ch; ++{ ++ tape[recorded++ % TAPELEN] = ch; ++ if(length < TAPELEN) ++ length++; ++ ++ return(ch); ++} ++ ++ ++/* ++ * playback user keystrokes ++ * ++ * Args: ch -- ignored ++ * ++ * Returns: character played back or -1 to indicate end of tape ++ */ ++int ++key_playback(ch) ++ int ch; ++{ ++ ch = length ? tape[(recorded + TAPELEN - length--) % TAPELEN] : -1; ++ return(ch); ++} ++ ++ ++ ++/*====================================================================== ++ Routines for painting the screen ++ - figure out what the terminal type is ++ - deal with screen size changes ++ - save special output sequences ++ - the usual screen clearing, cursor addressing and scrolling ++ ++ ++ This library gives programs the ability to easily access the ++ termcap information and write screen oriented and raw input ++ programs. The routines can be called as needed, except that ++ to use the cursor / screen routines there must be a call to ++ InitScreen() first. The 'Raw' input routine can be used ++ independently, however. (Elm comment) ++ ++ Not sure what the original source of this code was. It got to be ++ here as part of ELM. It has been changed significantly from the ++ ELM version to be more robust in the face of inconsistent terminal ++ autowrap behaviour. Also, the unused functions were removed, it was ++ made to pay attention to the window size, and some code was made nicer ++ (in my opinion anyways). It also outputs the terminal initialization ++ strings and provides for minimal scrolling and detects terminals ++ with out enough capabilities. (Pine comment, 1990) ++ ++ ++This code used to pay attention to the "am" auto margin and "xn" ++new line glitch fields, but they were so often incorrect because many ++terminals can be configured to do either that we've taken it out. It ++now assumes it dosn't know where the cursor is after outputing in the ++80th column. ++*/ ++ ++#define PUTLINE_BUFLEN 256 ++ ++static int _lines, _columns; ++static int _line = FARAWAY; ++static int _col = FARAWAY; ++static int _in_inverse; ++ ++ ++/* ++ * Internal prototypes ++ */ ++static void moveabsolute PROTO((int, int)); ++static void CursorUp PROTO((int)); ++static void CursorDown PROTO((int)); ++static void CursorLeft PROTO((int)); ++static void CursorRight PROTO((int)); ++ ++ ++extern char *_clearscreen, *_moveto, *_up, *_down, *_right, *_left, ++ *_setinverse, *_clearinverse, ++ *_setunderline, *_clearunderline, ++ *_setbold, *_clearbold, ++ *_cleartoeoln, *_cleartoeos, ++ *_startinsert, *_endinsert, *_insertchar, *_deletechar, ++ *_deleteline, *_insertline, ++ *_scrollregion, *_scrollup, *_scrolldown, ++ *_termcap_init, *_termcap_end; ++extern char term_name[]; ++extern int _tlines, _tcolumns; ++ ++static enum {NoScroll,UseScrollRegion,InsertDelete} _scrollmode; ++ ++char *tgoto(); /* and the goto stuff */ ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Initialize the screen for output, set terminal type, etc ++ ++ Args: tt -- Pointer to variable to store the tty output structure. ++ ++ Result: terminal size is discovered and set in pine state ++ termcap entry is fetched and stored ++ make sure terminal has adequate capabilites ++ evaluate scrolling situation ++ returns status of indicating the state of the screen/termcap entry ++ ++ Returns: ++ -1 indicating no terminal name associated with this shell, ++ -2..-n No termcap for this terminal type known ++ -3 Can't open termcap file ++ -4 Terminal not powerful enough - missing clear to eoln or screen ++ or cursor motion ++ ----*/ ++int ++config_screen(tt) ++ struct ttyo **tt; ++{ ++ struct ttyo *ttyo; ++ int err; ++ ++ ttyo = (struct ttyo *)fs_get(sizeof (struct ttyo)); ++ ++ _line = 0; /* where are we right now?? */ ++ _col = 0; /* assume zero, zero... */ ++ ++ /* ++ * This is an ugly hack to let vtterminalinfo know it's being called ++ * from pine. ++ */ ++ Pmaster = (PICO *)1; ++ if(err = vtterminalinfo(F_ON(F_TCAP_WINS, ps_global))) ++ return(err); ++ ++ Pmaster = NULL; ++ ++ if(_tlines <= 0) ++ _lines = DEFAULT_LINES_ON_TERMINAL; ++ else ++ _lines = _tlines; ++ ++ if(_tcolumns <= 0) ++ _columns = DEFAULT_COLUMNS_ON_TERMINAL; ++ else ++ _columns = _tcolumns; ++ ++ get_windsize(ttyo); ++ ++ ttyo->header_rows = 2; ++ ttyo->footer_rows = 3; ++ ++ /*---- Make sure this terminal has the capability. ++ All we need is cursor address, clear line, and ++ reverse video. ++ ---*/ ++ if(_moveto == NULL || _cleartoeoln == NULL || ++ _setinverse == NULL || _clearinverse == NULL) { ++ return(-4); ++ } ++ ++ dprint(1, (debugfile, "Terminal type: %s\n", term_name)); ++ ++ /*------ Figure out scrolling mode -----*/ ++ if(_scrollregion != NULL && _scrollregion[0] != '\0' && ++ _scrollup != NULL && _scrollup[0] != '\0'){ ++ _scrollmode = UseScrollRegion; ++ } else if(_insertline != NULL && _insertline[0] != '\0' && ++ _deleteline != NULL && _deleteline[0] != '\0') { ++ _scrollmode = InsertDelete; ++ } else { ++ _scrollmode = NoScroll; ++ } ++ dprint(7, (debugfile, "Scroll mode: %s\n", ++ _scrollmode==NoScroll ? "No Scroll" : ++ _scrollmode==InsertDelete ? "InsertDelete" : "Scroll Regions")); ++ ++ if (!_left) { ++ _left = "\b"; ++ } ++ ++ *tt = ttyo; ++ ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Initialize the screen with the termcap string ++ ----*/ ++void ++init_screen() ++{ ++ if(_termcap_init) /* init using termcap's rule */ ++ tputs(_termcap_init, 1, outchar); ++ ++ /* and make sure there are no scrolling surprises! */ ++ BeginScroll(0, ps_global->ttyo->screen_rows - 1); ++ /* and make sure icon text starts out consistent */ ++ icon_text(NULL); ++ fflush(stdout); ++} ++ ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Get the current window size ++ ++ Args: ttyo -- pointer to structure to store window size in ++ ++ NOTE: we don't override the given values unless we know better ++ ----*/ ++int ++get_windsize(ttyo) ++struct ttyo *ttyo; ++{ ++#ifdef RESIZING ++ struct winsize win; ++ ++ /* ++ * Get the window size from the tty driver. If we can't fish it from ++ * stdout (pine's output is directed someplace else), try stdin (which ++ * *must* be associated with the terminal; see init_tty_driver)... ++ */ ++ if(ioctl(1, TIOCGWINSZ, &win) >= 0 /* 1 is stdout */ ++ || ioctl(0, TIOCGWINSZ, &win) >= 0){ /* 0 is stdin */ ++ if(win.ws_row) ++ _lines = min(win.ws_row, MAX_SCREEN_ROWS); ++ ++ if(win.ws_col) ++ _columns = min(win.ws_col, MAX_SCREEN_COLS); ++ ++ dprint(2, (debugfile, "new win size -----<%d %d>------\n", ++ _lines, _columns)); ++ } ++ else ++ /* Depending on the OS, the ioctl() may have failed because ++ of a 0 rows, 0 columns setting. That happens on DYNIX/ptx 1.3 ++ (with a kernel patch that happens to involve the negotiation ++ of window size in the telnet streams module.) In this case ++ the error is EINVARG. Leave the default settings. */ ++ dprint(1, (debugfile, "ioctl(TIOCWINSZ) failed :%s\n", ++ error_description(errno))); ++#endif ++ ++ ttyo->screen_cols = min(_columns, MAX_SCREEN_COLS); ++ ttyo->screen_rows = min(_lines, MAX_SCREEN_ROWS); ++ return(0); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ End use of the screen. ++ Print status message, if any. ++ Flush status messages. ++ ----*/ ++void ++end_screen(message) ++ char *message; ++{ ++ int footer_rows_was_one = 0; ++ ++ dprint(9, (debugfile, "end_screen called\n")); ++ ++ if(FOOTER_ROWS(ps_global) == 1){ ++ footer_rows_was_one++; ++ FOOTER_ROWS(ps_global) = 3; ++ mark_status_unknown(); ++ } ++ ++ flush_status_messages(1); ++ blank_keymenu(_lines - 2, 0); ++ MoveCursor(_lines - 2, 0); ++ if(_termcap_end != NULL) ++ tputs(_termcap_end, 1, outchar); ++ ++ if(message){ ++ printf("%s\r\n", message); ++ } ++ ++ if(F_ON(F_ENABLE_XTERM_NEWMAIL, ps_global) && getenv("DISPLAY")) ++ icon_text("xterm"); ++ ++ fflush(stdout); ++ ++ if(footer_rows_was_one){ ++ FOOTER_ROWS(ps_global) = 1; ++ mark_status_unknown(); ++ } ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Clear the terminal screen ++ ++ Result: The screen is cleared ++ internal cursor position set to 0,0 ++ ----*/ ++void ++ClearScreen() ++{ ++ _line = 0; /* clear leaves us at top... */ ++ _col = 0; ++ ++ if(ps_global->in_init_seq) ++ return; ++ ++ mark_status_unknown(); ++ mark_keymenu_dirty(); ++ mark_titlebar_dirty(); ++ ++ if(!_clearscreen){ ++ ClearLines(0, _lines-1); ++ MoveCursor(0, 0); ++ } ++ else{ ++ tputs(_clearscreen, 1, outchar); ++ moveabsolute(0, 0); /* some clearscreens don't move correctly */ ++ } ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Internal move cursor to absolute position ++ ++ Args: col -- column to move cursor to ++ row -- row to move cursor to ++ ++ Result: cursor is moved (variables, not updates) ++ ----*/ ++ ++static void ++moveabsolute(col, row) ++{ ++ ++ char *stuff, *tgoto(); ++ ++ stuff = tgoto(_moveto, col, row); ++ tputs(stuff, 1, outchar); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Move the cursor to the row and column number ++ Args: row number ++ column number ++ ++ Result: Cursor moves ++ internal position updated ++ ----*/ ++void ++MoveCursor(row, col) ++ int row, col; ++{ ++ /** move cursor to the specified row column on the screen. ++ 0,0 is the top left! **/ ++ ++ int scrollafter = 0; ++ ++ /* we don't want to change "rows" or we'll mangle scrolling... */ ++ ++ if(ps_global->in_init_seq) ++ return; ++ ++ if (col < 0) ++ col = 0; ++ if (col >= ps_global->ttyo->screen_cols) ++ col = ps_global->ttyo->screen_cols - 1; ++ if (row < 0) ++ row = 0; ++ if (row > ps_global->ttyo->screen_rows) { ++ if (col == 0) ++ scrollafter = row - ps_global->ttyo->screen_rows; ++ row = ps_global->ttyo->screen_rows; ++ } ++ ++ if (!_moveto) ++ return; ++ ++ if (row == _line) { ++ if (col == _col) ++ return; /* already there! */ ++ ++ else if (abs(col - _col) < 5) { /* within 5 spaces... */ ++ if (col > _col && _right) ++ CursorRight(col - _col); ++ else if (col < _col && _left) ++ CursorLeft(_col - col); ++ else ++ moveabsolute(col, row); ++ } ++ else /* move along to the new x,y loc */ ++ moveabsolute(col, row); ++ } ++ else if (col == _col && abs(row - _line) < 5) { ++ if (row < _line && _up) ++ CursorUp(_line - row); ++ else if (_line > row && _down) ++ CursorDown(row - _line); ++ else ++ moveabsolute(col, row); ++ } ++ else if (_line == row-1 && col == 0) { ++ putchar('\n'); /* that's */ ++ putchar('\r'); /* easy! */ ++ } ++ else ++ moveabsolute(col, row); ++ ++ _line = row; /* to ensure we're really there... */ ++ _col = col; ++ ++ if (scrollafter) { ++ while (scrollafter--) { ++ putchar('\n'); ++ putchar('\r'); ++ ++ } ++ } ++ ++ return; ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Newline, move the cursor to the start of next line ++ ++ Result: Cursor moves ++ ----*/ ++void ++NewLine() ++{ ++ /** move the cursor to the beginning of the next line **/ ++ ++ Writechar('\n', 0); ++ Writechar('\r', 0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Move cursor up n lines with terminal escape sequence ++ ++ Args: n -- number of lines to go up ++ ++ Result: cursor moves, ++ internal position updated ++ ++ Only for ttyout use; not outside callers ++ ----*/ ++static void ++CursorUp(n) ++int n; ++{ ++ /** move the cursor up 'n' lines **/ ++ /** Calling function must check that _up is not null before calling **/ ++ ++ _line = (_line-n > 0? _line - n: 0); /* up 'n' lines... */ ++ ++ while (n-- > 0) ++ tputs(_up, 1, outchar); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Move cursor down n lines with terminal escape sequence ++ ++ Arg: n -- number of lines to go down ++ ++ Result: cursor moves, ++ internal position updated ++ ++ Only for ttyout use; not outside callers ++ ----*/ ++static void ++CursorDown(n) ++ int n; ++{ ++ /** move the cursor down 'n' lines **/ ++ /** Caller must check that _down is not null before calling **/ ++ ++ _line = (_line+n < ps_global->ttyo->screen_rows ? _line + n ++ : ps_global->ttyo->screen_rows); ++ /* down 'n' lines... */ ++ ++ while (n-- > 0) ++ tputs(_down, 1, outchar); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Move cursor left n lines with terminal escape sequence ++ ++ Args: n -- number of lines to go left ++ ++ Result: cursor moves, ++ internal position updated ++ ++ Only for ttyout use; not outside callers ++ ----*/ ++static void ++CursorLeft(n) ++int n; ++{ ++ /** move the cursor 'n' characters to the left **/ ++ /** Caller must check that _left is not null before calling **/ ++ ++ _col = (_col - n> 0? _col - n: 0); /* left 'n' chars... */ ++ ++ while (n-- > 0) ++ tputs(_left, 1, outchar); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Move cursor right n lines with terminal escape sequence ++ ++ Args: number of lines to go right ++ ++ Result: cursor moves, ++ internal position updated ++ ++ Only for ttyout use; not outside callers ++ ----*/ ++static void ++CursorRight(n) ++int n; ++{ ++ /** move the cursor 'n' characters to the right (nondestructive) **/ ++ /** Caller must check that _right is not null before calling **/ ++ ++ _col = (_col+n < ps_global->ttyo->screen_cols? _col + n : ++ ps_global->ttyo->screen_cols); /* right 'n' chars... */ ++ ++ while (n-- > 0) ++ tputs(_right, 1, outchar); ++ ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Start painting inverse on the screen ++ ++ Result: escape sequence to go into inverse is output ++ returns 1 if it was done, 0 if not. ++ ----*/ ++int ++StartInverse() ++{ ++ /** set inverse video mode **/ ++ ++ if (!_setinverse) ++ return(0); ++ ++ if(_in_inverse) ++ return(1); ++ ++ _in_inverse = 1; ++ tputs(_setinverse, 1, outchar); ++ return(1); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ End painting inverse on the screen ++ ++ Result: escape sequence to go out of inverse is output ++ returns 1 if it was done, 0 if not. ++ ----------------------------------------------------------------------*/ ++void ++EndInverse() ++{ ++ /** compliment of startinverse **/ ++ ++ if (!_clearinverse) ++ return; ++ ++ if(_in_inverse){ ++ _in_inverse = 0; ++ tputs(_clearinverse, 1, outchar); ++ } ++} ++ ++ ++int ++StartUnderline() ++{ ++ if (!_setunderline) ++ return(0); ++ ++ tputs(_setunderline, 1, outchar); ++ return(1); ++} ++ ++ ++void ++EndUnderline() ++{ ++ if (!_clearunderline) ++ return; ++ ++ tputs(_clearunderline, 1, outchar); ++} ++ ++int ++StartBold() ++{ ++ if (!_setbold) ++ return(0); ++ ++ tputs(_setbold, 1, outchar); ++ return(1); ++} ++ ++void ++EndBold() ++{ ++ if (!_clearbold) ++ return; ++ ++ tputs(_clearbold, 1, outchar); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Insert character on screen pushing others right ++ ++ Args: c -- character to insert ++ ++ Result: charcter is inserted if possible ++ return -1 if it can't be done ++ ----------------------------------------------------------------------*/ ++InsertChar(c) ++ int c; ++{ ++ if(_insertchar != NULL && *_insertchar != '\0') { ++ tputs(_insertchar, 1, outchar); ++ Writechar(c, 0); ++ } else if(_startinsert != NULL && *_startinsert != '\0') { ++ tputs(_startinsert, 1, outchar); ++ Writechar(c, 0); ++ tputs(_endinsert, 1, outchar); ++ } else { ++ return(-1); ++ } ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Delete n characters from line, sliding rest of line left ++ ++ Args: n -- number of characters to delete ++ ++ ++ Result: characters deleted on screen ++ returns -1 if it wasn't done ++ ----------------------------------------------------------------------*/ ++DeleteChar(n) ++ int n; ++{ ++ if(_deletechar == NULL || *_deletechar == '\0') ++ return(-1); ++ ++ while(n) { ++ tputs(_deletechar, 1, outchar); ++ n--; ++ } ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Go into scrolling mode, that is set scrolling region if applicable ++ ++ Args: top -- top line of region to scroll ++ bottom -- bottom line of region to scroll ++ (These are zero-origin numbers) ++ ++ Result: either set scrolling region or ++ save values for later scrolling ++ returns -1 if we can't scroll ++ ++ Unfortunately this seems to leave the cursor in an unpredictable place ++ at least the manuals don't say where, so we force it here. ++-----*/ ++static int __t, __b; ++ ++BeginScroll(top, bottom) ++ int top, bottom; ++{ ++ char *stuff; ++ ++ if(_scrollmode == NoScroll) ++ return(-1); ++ ++ __t = top; ++ __b = bottom; ++ if(_scrollmode == UseScrollRegion){ ++ stuff = tgoto(_scrollregion, bottom, top); ++ tputs(stuff, 1, outchar); ++ /*-- a location very far away to force a cursor address --*/ ++ _line = FARAWAY; ++ _col = FARAWAY; ++ } ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ End scrolling -- clear scrolling regions if necessary ++ ++ Result: Clear scrolling region on terminal ++ -----*/ ++void ++EndScroll() ++{ ++ if(_scrollmode == UseScrollRegion && _scrollregion != NULL){ ++ /* Use tgoto even though we're not cursor addressing because ++ the format of the capability is the same. ++ */ ++ char *stuff = tgoto(_scrollregion, ps_global->ttyo->screen_rows -1, 0); ++ tputs(stuff, 1, outchar); ++ /*-- a location very far away to force a cursor address --*/ ++ _line = FARAWAY; ++ _col = FARAWAY; ++ } ++} ++ ++ ++/* ---------------------------------------------------------------------- ++ Scroll the screen using insert/delete or scrolling regions ++ ++ Args: lines -- number of lines to scroll, positive forward ++ ++ Result: Screen scrolls ++ returns 0 if scroll succesful, -1 if not ++ ++ positive lines goes foward (new lines come in at bottom ++ Leaves cursor at the place to insert put new text ++ ++ 0,0 is the upper left ++ -----*/ ++ScrollRegion(lines) ++ int lines; ++{ ++ int l; ++ ++ if(lines == 0) ++ return(0); ++ ++ if(_scrollmode == UseScrollRegion) { ++ if(lines > 0) { ++ MoveCursor(__b, 0); ++ for(l = lines ; l > 0 ; l--) ++ tputs((_scrolldown == NULL || _scrolldown[0] =='\0') ? "\n" : ++ _scrolldown, 1, outchar); ++ } else { ++ MoveCursor(__t, 0); ++ for(l = -lines; l > 0; l--) ++ tputs(_scrollup, 1, outchar); ++ } ++ } else if(_scrollmode == InsertDelete) { ++ if(lines > 0) { ++ MoveCursor(__t, 0); ++ for(l = lines; l > 0; l--) ++ tputs(_deleteline, 1, outchar); ++ MoveCursor(__b, 0); ++ for(l = lines; l > 0; l--) ++ tputs(_insertline, 1, outchar); ++ } else { ++ for(l = -lines; l > 0; l--) { ++ MoveCursor(__b, 0); ++ tputs(_deleteline, 1, outchar); ++ MoveCursor(__t, 0); ++ tputs(_insertline, 1, outchar); ++ } ++ } ++ } else { ++ return(-1); ++ } ++ fflush(stdout); ++ return(0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Write a character to the screen, keeping track of cursor position ++ ++ Args: ch -- character to output ++ ++ Result: character output ++ cursor position variables updated ++ ----*/ ++void ++Writechar(ch, new_esc_len) ++ register unsigned int ch; ++ int new_esc_len; ++{ ++ static int esc_len = 0; ++ ++ if(ps_global->in_init_seq /* silent */ ++ || (F_ON(F_BLANK_KEYMENU, ps_global) /* or bottom, */ ++ && !esc_len /* right cell */ ++ && _line + 1 == ps_global->ttyo->screen_rows ++ && _col + 1 == ps_global->ttyo->screen_cols)) ++ return; ++ ++ if(!iscntrl(ch & 0x7f)){ ++ putchar(ch); ++ if(esc_len > 0) ++ esc_len--; ++ else ++ _col++; ++ } ++ else{ ++ switch(ch){ ++ case LINE_FEED: ++ /*-- Don't have to watch out for auto wrap or newline glitch ++ because we never let it happen. See below ++ ---*/ ++ putchar('\n'); ++ _line = min(_line+1,ps_global->ttyo->screen_rows); ++ esc_len = 0; ++ break; ++ ++ case RETURN : /* move to column 0 */ ++ putchar('\r'); ++ _col = 0; ++ esc_len = 0; ++ break; ++ ++ case BACKSPACE : /* move back a space if not in column 0 */ ++ if(_col != 0) { ++ putchar('\b'); ++ _col--; ++ } /* else BACKSPACE does nothing */ ++ ++ break; ++ ++ case BELL : /* ring the bell but don't advance _col */ ++ putchar(ch); ++ break; ++ ++ case TAB : /* if a tab, output it */ ++ do /* BUG? ignores tty driver's spacing */ ++ putchar(' '); ++ while(_col < ps_global->ttyo->screen_cols - 1 ++ && ((++_col)&0x07) != 0); ++ break; ++ ++ case ESCAPE : ++ /* If we're outputting an escape here, it may be part of an iso2022 ++ escape sequence in which case take up no space on the screen. ++ Unfortunately such sequences are variable in length. ++ */ ++ esc_len = new_esc_len - 1; ++ putchar(ch); ++ break; ++ ++ default : /* Change remaining control characters to ? */ ++ if(F_ON(F_PASS_CONTROL_CHARS, ps_global)) ++ putchar(ch); ++ else ++ putchar('?'); ++ ++ if(esc_len > 0) ++ esc_len--; + else -+ exit_to_main = 1; - - break; - -@@ -1852,6 +1859,7 @@ - /*--------------------- Key right -------------------*/ - case MC_CHARRIGHT : - case MC_NEXTITEM : -+ exit_to_main = 0; - if(n = folder_lister_next(fs)) - fs->folder_index = n; - -@@ -1873,7 +1881,7 @@ - rc--; - - if(rc < 0){ -- q_status_message(SM_ORDER, 0, 1, "Already on first line."); -+ q_status_message(SM_ORDER, 0, 1, "已經到第一行了。"); - if(fs->top_row != 0){ /* make sure! */ - fs->top_row = 0; - fs->prev_index = -1; -@@ -1919,7 +1927,7 @@ - FOLDERS(fs->context))->d_line + 1; - - if(rc > fs->last_row){ -- q_status_message(SM_ORDER, 0, 1, "Already on last line."); -+ q_status_message(SM_ORDER, 0, 1, "已經到最後一行了。"); - break; - } - -@@ -2038,7 +2046,7 @@ - rc++; - } - else -- q_status_message(SM_ORDER,0,1,"Already on first page."); -+ q_status_message(SM_ORDER,0,1,"已經到第一頁了。"); - - break; - -@@ -2051,7 +2059,7 @@ - if((rc = fs->top_row + fs->display_rows) > fs->last_row){ - if((int)folder_entry(fs->folder_index, - FOLDERS(fs->context))->d_line >= fs->last_row){ -- q_status_message(SM_ORDER,0,1,"Already on last page."); -+ q_status_message(SM_ORDER,0,1,"已經到最後一頁了。"); - break; - } - else -@@ -2092,7 +2100,7 @@ - } - else - q_status_message(SM_ORDER | SM_DING, 3, 3, -- "Sorry, no help text available"); -+ "很抱歉,文字說明無法取得"); - - break; - -@@ -2101,7 +2109,7 @@ - case MC_CHOICE : - if(!folder_total(FOLDERS(fs->context))){ - q_status_message(SM_ORDER | SM_DING, 3, 3, -- "Empty folder collection. Nothing to select!"); -+ "空的資料匣總集。沒有東西可供選擇!"); - } - else if(folder_lister_select(cur_f, fs, listmode)){ - mangled_footer++; -@@ -2163,7 +2171,7 @@ - mangled_footer++; - } - else -- q_status_message(SM_ORDER, 0, 4, "Already in List Mode"); -+ q_status_message(SM_ORDER, 0, 4, "已經在列表模式了。"); - - break; - -@@ -2296,8 +2304,8 @@ - case MC_DELETE : - if(!ALL_FOUND(fs->context) || (fs->context->use & CNTXT_PSEUDO)){ - q_status_message1(SM_ORDER | SM_DING, 0, 3, -- "No folder selected to delete. %s list.", -- ALL_FOUND(fs->context) ? "Empty" : "Expand"); -+ "尚未選擇供刪除的資料匣。%s列表。", -+ ALL_FOUND(fs->context) ? "空" : "展開"); - break; - } - -@@ -2375,13 +2383,13 @@ - if(fs->zoomed = !fs->zoomed){ /* clear all the prefixes */ - (void) folder_lister_nearest_selected(fs); - q_status_message1(SM_ORDER, 0, 3, -- "In Zoomed list of %s folders. Use \"Z\" to restore regular list", -+ "在 %s 個資料匣縮放的列表中。使用 \"Z\" 來回復正常列表", - int2string(n)); - - } - else{ - q_status_message(SM_ORDER, 0, 3, -- "Folder List Zoom mode is now off"); -+ "資料匣列表縮放模式目前為關閉狀態"); - } - - create_folder_display(fs, ps->ttyo->screen_cols); -@@ -2389,7 +2397,7 @@ - } - else - q_status_message(SM_ORDER, 0, 3, -- "No selected folders to Zoom on"); -+ "尚未選擇供放大的資料匣。"); - - - break; -@@ -2403,12 +2411,12 @@ - break; - - case 0 : -- q_status_message(SM_ORDER | SM_DING, 0, 2, "Word not found"); -+ q_status_message(SM_ORDER | SM_DING, 0, 2, "找不到該字"); - break; - - case 2 : - q_status_message(SM_ORDER, 0, 2, -- "Search wrapped to beginning"); -+ "從頭搜尋"); - break; - } - -@@ -2469,8 +2477,8 @@ - && ALL_FOUND(fs->context)) - return(fs->folder_index + 1); - -- q_status_message1(SM_ORDER, 0, 1, "Already on last %sfolder", -- fs->zoomed ? "Zoomed " : ""); -+ q_status_message1(SM_ORDER, 0, 1, "已經在最後一個%s資料匣了", -+ fs->zoomed ? "縮放後的" : ""); - return(0); - } - -@@ -2491,8 +2499,8 @@ - else if(fs->folder_index > 0 && ALL_FOUND(fs->context)) - return(fs->folder_index - 1); - -- q_status_message1(SM_ORDER, 0, 1, "Already on first %sfolder", -- fs->zoomed ? "Zoomed " : ""); -+ q_status_message1(SM_ORDER, 0, 1, "已經在第一個%s資料匣了", -+ fs->zoomed ? "縮放後的" : ""); - return(-1); - } - -@@ -2507,7 +2515,7 @@ - if(listmode){ - if(NEWS_TEST(fs->context) && !strncmp(f->prefix, "SUB", 3)){ - q_status_message1(SM_ORDER, 0, 3, -- "Already subscribed to \"%s\"", -+ "已訂閱\至 \"%s\"", - FLDR_NAME(f)); - } - else{ -@@ -2517,7 +2525,7 @@ - } - else{ - if(f->isdir){ -- q_status_message(SM_ORDER, 0, 3, "Can't select directories"); -+ q_status_message(SM_ORDER, 0, 3, "無法選擇目錄"); - } - else - folder_select_toggle(fs, f); -@@ -2574,13 +2582,13 @@ - extern char *sel_pmt2; - - if((f = folder_entry(fs->folder_index, FOLDERS(fs->context)))->isdir){ -- q_status_message(SM_ORDER | SM_DING, 0, 3, "Can't Select directories"); -+ q_status_message(SM_ORDER | SM_DING, 0, 3, "無法選擇目錄"); - return(0); - } - - sel_opts = self_opts2; - if(old_tot = selected_folders(fs)){ -- sel_opts1[1].label = "unselect Cur" + (f->selected ? 0 : 2); -+ sel_opts1[1].label = "取消目前選擇" + (f->selected ? 0 : 2); - sel_opts += 2; /* disable extra options */ - switch(q = radio_buttons(SEL_ALTER_PMT, -FOOTER_ROWS(ps_global), - sel_opts1, 'c', 'x', help, RB_NORM)){ -@@ -2605,7 +2613,7 @@ - - default : - q_status_message(SM_ORDER | SM_DING, 3, 3, -- "Unsupported Select option"); -+ "未支援的選項"); - return(0); - } - } -@@ -2641,10 +2649,10 @@ - create_folder_display(fs, ps_global->ttyo->screen_cols); - } - -- q_status_message4(SM_ORDER, 0, 2, "%s%s folder%s %sselected", -- old_tot ? "" : "All ", -+ q_status_message3(SM_ORDER, 0, 2, "%s%s 個資料匣已被%s選擇", -+ old_tot ? "" : "全部 ", - comatose(old_tot ? old_tot : n), -- plural(old_tot ? old_tot : n), old_tot ? "UN" : ""); -+ old_tot ? "取消" : ""); - return(1); - - case 't' : /* Text */ -@@ -2661,7 +2669,7 @@ - - default : - q_status_message(SM_ORDER | SM_DING, 3, 3, -- "Unsupported Select option"); -+ "未支援的選項"); - return(0); - } - -@@ -2690,35 +2698,30 @@ - - if(!(diff = (total = selected_folders(fs)) - old_tot)){ - if(narrow) -- q_status_message4(SM_ORDER, 0, 2, -- "%s. %s folder%s remain%s selected.", -- j ? "No change resulted" -- : "No messages in intersection", -- comatose(old_tot), plural(old_tot), -- (old_tot == 1L) ? "s" : ""); -+ q_status_message2(SM_ORDER, 0, 2, -+ "%s。 仍有 %s 個資料匣被選擇。", -+ j ? "沒有導致改變" -+ : "交點中沒有信件", comatose(old_tot)); - else if(old_tot && j) - q_status_message(SM_ORDER, 0, 2, -- "No change resulted. Matching folders already selected."); -+ "沒有導致改變。符合的資料匣已經被選擇了。"); - else - q_status_message1(SM_ORDER | SM_DING, 0, 2, -- "Select failed! No %sfolders selected.", -- old_tot ? "additional " : ""); -+ "選擇失敗!沒有%s資料匣被選擇。", -+ old_tot ? "額外的 " : ""); - } - else{ - if(old_tot){ - sprintf(tmp_20k_buf, -- "Select matched %ld folder%s! %s %sfolder%s %sselected.", -+ "選擇符合的 %ld 個資料匣!共計 %s 個資料匣被%s選擇.", - (diff > 0) ? diff : old_tot + diff, -- plural((diff > 0) ? diff : old_tot + diff), - comatose((diff > 0) ? total : -diff), -- (diff > 0) ? "total " : "", -- plural((diff > 0) ? total : -diff), -- (diff > 0) ? "" : "UN"); -+ (diff > 0) ? "" : "取消"); - q_status_message(SM_ORDER, 0, 2, tmp_20k_buf); - } - else{ -- q_status_message2(SM_ORDER, 0, 2, "Select matched %s folder%s!", -- comatose(diff), plural(diff)); -+ q_status_message1(SM_ORDER, 0, 2, "選擇符合的 %s 個資料匣!", -+ comatose(diff)); - - if(F_OFF(F_SELECTED_SHOWN_BOLD, ps_global)){ - folder_prefixes(fs, " "); -@@ -2833,7 +2836,7 @@ - fp = next_folder_dir(fs->context, tmpf->name); - - /* Provide context in new collection header */ -- sprintf(tmp_20k_buf, "Dir: %s", -+ sprintf(tmp_20k_buf, "目錄:%s", - ((p = strstr(fs->context->context, "%s")) && !*(p+2) - && !strncmp(fp->ref, fs->context->context, - p - fs->context->context)) -@@ -2854,9 +2857,9 @@ - - fs->prev_index = -1; /* redraw display */ - -- q_status_message2(SM_ORDER, 0, 3, "Now in %sdirectory: %s", -+ q_status_message2(SM_ORDER, 0, 3, "目前在 %s目錄中:%s", - folder_total(FOLDERS(fs->context)) -- ? "" : "EMPTY ", fp->ref); -+ ? "" : "空的 ", fp->ref); - rv++; - } - else -@@ -2902,12 +2905,12 @@ - fs->prev_index = -1; /* redraw display */ - - if(fp->status & CNTXT_SUBDIR) -- q_status_message1(SM_ORDER, 0, 3, "Now in directory: %s", -+ q_status_message1(SM_ORDER, 0, 3, "目前所在目錄:%s", - strsquish(tmp_20k_buf + 500, fp->ref, - fs->display_cols - 22)); - else - q_status_message(SM_ORDER, 0, 3, -- "Returned to collection's top directory"); -+ "回到總集的最上層目錄"); - - rv++; - } -@@ -3473,12 +3476,12 @@ - if(fs->context->use & CNTXT_INCMNG){ - char inbox_host[MAXPATH], *beg, *end = NULL; - ESCKEY_S *special_key; -- static ESCKEY_S host_key[] = {{ctrl('X'),12,"^X","Use Inbox Host"}, -+ static ESCKEY_S host_key[] = {{ctrl('X'),12,"^X","使用 Inbox 的主機"}, - {-1, 0, NULL, NULL}}; - - if(ps_global->readonly_pinerc){ - q_status_message(SM_ORDER,3,5, -- "Addition cancelled: config file not editable"); -+ "取消新增:無法編輯設定檔"); - return(FALSE); - } - -@@ -3500,7 +3503,7 @@ - else - special_key = NULL; - -- sprintf(tmp, "Name of server to contain added folder : "); -+ sprintf(tmp, "包含新增資料匣的主機名:"); - help = NO_HELP; - while(1){ - int flags = OE_APPEND_CURRENT; -@@ -3518,7 +3521,7 @@ - } - else if(rc == 1){ - q_status_message(SM_ORDER,0,2, -- "Addition of new folder cancelled"); -+ "取消新增資料匣"); - return(FALSE); - } - else if(rc == 0) -@@ -3528,7 +3531,7 @@ - - if(offset = strlen(add_folder)){ /* must be host for incoming */ - int i; -- sprintf(tmp, "Folder on \"%s\" to add : ", add_folder); -+ sprintf(tmp, "加入在 \"%s\" 上的資料匣:", add_folder); - for(i = offset;i >= 0; i--) - add_folder[i+1] = add_folder[i]; - -@@ -3537,7 +3540,7 @@ - add_folder[++offset] = '\0'; /* +2, total */ - } - else -- sprintf(tmp, "Folder name to add : "); -+ sprintf(tmp, "新增資料匣名稱:"); - - help = NO_HELP; - while(1){ -@@ -3545,14 +3548,14 @@ - - p = NULL; - if(isdir){ -- add_key[0].label = "Create Folder"; -+ add_key[0].label = "建立資料匣"; - if(tmp[0] == 'F') -- rplstr(tmp, 6, "Directory"); -+ rplstr(tmp, 6, "目錄"); - } - else{ -- add_key[0].label = "Create Directory"; -+ add_key[0].label = "建立目錄"; - if(tmp[0] == 'D') -- rplstr(tmp, 9, "Folder"); -+ rplstr(tmp, 9, "資料匣"); - } - - flags = OE_APPEND_CURRENT; -@@ -3566,7 +3569,7 @@ - if(!ps_global->show_dot_names && add_folder[offset] == '.'){ - if(cnt++ <= 0) - q_status_message(SM_ORDER,3,3, -- "Folder name can't begin with dot"); -+ "資料匣不能以點 \".\" 為名稱開頭"); - else{ - NAMEVAL_S *feat; - int i; -@@ -3576,7 +3579,7 @@ - ;/* do nothing */ - - q_status_message1(SM_ORDER,3,3, -- "Config feature \"%s\" enables names beginning with dot", -+ "設定檔中 \"%s\" 的功\能可使資料匣以點 \".\" 為名稱開頭", - feat && feat->name ? feat->name : ""); - } - -@@ -3596,7 +3599,7 @@ - } - else if(*p == fs->context->dir->delim){ - q_status_message(SM_ORDER|SM_DING, 3, 3, -- "Can't have trailing directory delimiters!"); -+ "不能有目錄尾端的分隔號 \"/\"!"); - display_message('X'); - continue; - } -@@ -3618,7 +3621,7 @@ - : NO_HELP; - } - else if(rc == 1 || add_folder[0] == '\0') { -- q_status_message(SM_ORDER,0,2, "Addition of new folder cancelled"); -+ q_status_message(SM_ORDER,0,2, "取消新增資料匣"); - return(FALSE); - } - } -@@ -3637,7 +3640,7 @@ - - help = NO_HELP; - if(fs->context->use & CNTXT_INCMNG){ -- sprintf(tmp, "Nickname for folder \"%s\" : ", &add_folder[offset]); -+ sprintf(tmp, "資料匣\"%s\" 的暱稱:", &add_folder[offset]); - while(1){ - int flags = OE_APPEND_CURRENT; - -@@ -3658,7 +3661,7 @@ - } - else if(rc == 1 || (rc != 3 && !*nickname)){ - q_status_message(SM_ORDER,0,2, -- "Addition of new folder cancelled"); -+ "取消新增資料匣"); - return(FALSE); - } - } -@@ -3673,7 +3676,7 @@ - f = folder_entry(offset, FOLDERS(fs->context)); - if(!strucmp(FLDR_NAME(f), nickname[0] ? nickname : add_folder)){ - q_status_message1(SM_ORDER | SM_DING, 0, 3, -- "Incoming folder \"%s\" already exists", -+ "新進資料匣(Incoming folder) \"%s\" 已存在", - nickname[0] ? nickname : add_folder); - return(FALSE); - } -@@ -3726,7 +3729,7 @@ - if(nickname[0]) - strcpy(add_folder, nickname); /* known by new name */ - -- q_status_message1(SM_ORDER, 0, 3, "Folder \"%s\" created",add_folder); -+ q_status_message1(SM_ORDER, 0, 3, "資料匣 \"%s\" 已建立",add_folder); - return_val = add_folder; - } - else if(context_isambig(add_folder)){ -@@ -3742,15 +3745,15 @@ - */ - refresh_folder_list(fs, TRUE); - -- q_status_message2(SM_ORDER, 0, 3, "%s \"%s\" created", -- isdir ? "Directory" : "Folder", add_folder); -+ q_status_message2(SM_ORDER, 0, 3, "%s \"%s\" 已建立", -+ isdir ? "目錄" : "資料匣", add_folder); - } - - return_val = add_folder; - } - else - q_status_message1(SM_ORDER, 0, 3, -- "Folder \"%s\" created outside current collection", -+ "資料匣 \"%s\" 建立於目前的總集之外", - add_folder); - - return(return_val != NULL); -@@ -3789,13 +3792,13 @@ - subscribe_keys[i = 0].ch = ctrl('T'); - subscribe_keys[i].rval = 12; - subscribe_keys[i].name = "^T"; -- subscribe_keys[i++].label = "To All Grps"; -+ subscribe_keys[i++].label = "給所有的組群"; - - if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ - subscribe_keys[i].ch = ctrl('I'); - subscribe_keys[i].rval = 11; - subscribe_keys[i].name = "TAB"; -- subscribe_keys[i++].label = "Complete"; -+ subscribe_keys[i++].label = "完成"; - } - - subscribe_keys[i].ch = -1; -@@ -3855,7 +3858,7 @@ - } - else{ - q_status_message(SM_ORDER, 0, 2, -- "No group substring to match! Use ^T to list all news groups."); -+ "沒有符合的組群字串!請用 ^T 列出所有新聞組群。"); - continue; - } - -@@ -3908,10 +3911,10 @@ - else{ - if(rc == 12) - q_status_message(SM_ORDER | SM_DING, 3, 3, -- "No groups to select from!"); -+ "沒有可供選擇的組群!"); - else - q_status_message1(SM_ORDER, 3, 3, -- "News group \"%s\" didn't match any existing groups", -+ "新聞組群 \"%s\" 不符合任何現存的組群", - folder); - - continue; -@@ -3959,7 +3962,7 @@ - - if(rc < 0){ - if(rc == -1) -- q_status_message(SM_ORDER, 0, 3, "Subscribe cancelled"); -+ q_status_message(SM_ORDER, 0, 3, "取消訂閱\"); - } - else{ - if(folders){ /*------ Actually do the subscription -----*/ -@@ -3981,7 +3984,7 @@ - */ - q_status_message1(errors ?SM_INFO : SM_ORDER, - errors ? 0 : 3, 3, -- "Error subscribing to \"%s\"", -+ "訂閱\至 \"%s\" 時發生錯誤", - (char *) flp->text.data); - errors++; - } -@@ -4005,13 +4008,13 @@ - - if(n == 0) - q_status_message(SM_ORDER | SM_DING, 3, 5, -- "Subscriptions failed, subscribed to no new groups"); -+ "訂閱\失敗,沒有訂閱\任何新組群"); - else - q_status_message3(SM_ORDER | (errors ? SM_DING : 0), - errors ? 3 : 0,3, -- "Subscribed to %s new groups%s%s", -+ "訂閱\至 %s 個新組群%s%s", - comatose((long)n), -- errors ? ", failed on " : "", -+ errors ? ",發生錯誤於 " : "", - errors ? comatose((long)errors) : ""); - - mail_free_stringlist(&folders); -@@ -4020,7 +4023,7 @@ - (void) context_apply(tmp_20k_buf, &subscribe_cntxt, folder); - if(mail_subscribe(NULL, tmp_20k_buf) == 0L){ - q_status_message1(SM_ORDER | SM_DING, 3, 3, -- "Error subscribing to \"%s\"", folder); -+ "訂閱\至 \"%s\" 時發生錯誤", folder); - } - else if(ALL_FOUND(cntxt)){ - /*---- Update the screen display data structures -----*/ -@@ -4034,7 +4037,7 @@ - } - - if(folder[0]) -- q_status_message1(SM_ORDER, 0, 3, "Subscribed to \"%s\"", folder); -+ q_status_message1(SM_ORDER, 0, 3, "訂閱\至 \"%s\"", folder); - } - - free_fdir(&subscribe_cntxt.dir, 1); -@@ -4074,19 +4077,19 @@ - - if(NEWS_TEST(fs->context)){ - q_status_message(SM_ORDER | SM_DING, 3, 3, -- "Can't rename bulletin boards or news groups!"); -+ "無法更改電子佈告欄或新聞組群的名稱!"); - return(0); - } - else if(!folder_total(FOLDERS(fs->context))){ - q_status_message(SM_ORDER | SM_DING, 0, 4, -- "Empty folder collection. No folder to rename!"); -+ "空的資料匣總集。沒有可供更名的資料匣!"); - return(0); - } - else if((new_f = folder_entry(fs->folder_index, FOLDERS(fs->context))) - && (!strucmp(FLDR_NAME(new_f), ps_global->inbox_name) - || new_f->parent)) { - q_status_message1(SM_ORDER | SM_DING, 3, 4, -- "Can't change special folder name \"%s\"", -+ "無法更改特殊資料匣 \"%s\" 的名稱", - new_f->parent - ? new_f->nickname - : ps_global->inbox_name); -@@ -4102,11 +4105,11 @@ - - ren_cur = strcmp(folder, ps_global->cur_folder) == 0; - -- sprintf(prompt, "Rename %s to : ", -+ sprintf(prompt, "將 %s 更名為:", - (fs->context->use & CNTXT_INCMNG) -- ? "nickname" -+ ? "暱稱" - : (isdir = new_f->isdir) -- ? "directory" : "folder"); -+ ? "目錄" : "資料匣"); - help = NO_HELP; - strcpy(new_name, folder); - while(1) { -@@ -4127,7 +4130,7 @@ - if(!ps_global->show_dot_names && *new_name == '.'){ - if(cnt++ <= 0) - q_status_message(SM_ORDER,3,3, -- "Folder name can't begin with dot"); -+ "資料匣不能以點 \".\" 為名稱開頭"); - else{ - NAMEVAL_S *feat; - int i; -@@ -4137,7 +4140,7 @@ - ;/* do nothing */ - - q_status_message1(SM_ORDER,3,3, -- "Config feature \"%s\" enables names beginning with dot", -+ "設定檔中 \"%s\" 的功\能可使資料匣以點 \".\" 為名稱開頭", - feat && feat->name ? feat->name : ""); - } - -@@ -4147,13 +4150,13 @@ - - if(folder_index(new_name, fs->context, FI_ANY) >= 0){ - q_status_message1(SM_ORDER, 3, 3, -- "Folder \"%s\" already exists", -+ "資料匣 \"%s\" 已存在", - pretty_fn(new_name)); - display_message(NO_OP_COMMAND); - continue; - } - else if(!strucmp(new_name, ps_global->inbox_name)){ -- q_status_message1(SM_ORDER, 3, 3, "Can't rename folder to %s", -+ q_status_message1(SM_ORDER, 3, 3, "無法將資料匣更名至 %s", - ps_global->inbox_name); - display_message(NO_OP_COMMAND); - continue; -@@ -4176,7 +4179,7 @@ - if(rc == 1 - || !(*new_name || (fs->context->use & CNTXT_INCMNG)) - || !strcmp(new_name, folder)){ -- q_status_message(SM_ORDER, 0, 2, "Folder rename cancelled"); -+ q_status_message(SM_ORDER, 0, 2, "取消資料匣更名"); - return(0); - } - -@@ -4258,7 +4261,7 @@ - /* renaming sent-mail or saved-messages */ - if(context_create(fs->context, NULL, folder)){ - q_status_message3(SM_ORDER,0,3, -- "Folder \"%s\" renamed to \"%s\". New \"%s\" created", -+ "資料匣 \"%s\" 名稱改為 \"%s\"。建立新的 \"%s\"", - folder, new_name, - pretty_fn( - (strcmp(ps_global->VAR_DEFAULT_SAVE_FOLDER, -@@ -4269,7 +4272,7 @@ - } - else{ - q_status_message1(SM_ORDER | SM_DING, 3, 4, -- "Error creating new \"%s\"", folder); -+ "建立新的資料匣 \"%s\" 時發生錯誤", folder); - - dprint(2, (debugfile, "Error creating \"%s\" in %s context\n", - folder, fs->context->context)); -@@ -4277,7 +4280,7 @@ - } - else - q_status_message2(SM_ORDER, 0, 3, -- "Folder \"%s\" renamed to \"%s\"", -+ "資料匣 \"%s\" 名稱改為 \"%s\"", - pretty_fn(folder), pretty_fn(new_name)); - - /* Rebuild folder list */ -@@ -4326,7 +4329,7 @@ - int ret, close_opened = 0, blast_folder = 1; - - if(NEWS_TEST(fs->context)){ -- static char fmt[] = "Really unsubscribe from \"%.*s\""; -+ static char fmt[] = "確定自 \"%.*s\" 中解除訂閱\嗎"; - - folder = folder_entry(fs->folder_index, FOLDERS(fs->context))->name; - /* 4 is strlen("%.*s") */ -@@ -4348,7 +4351,7 @@ - (void) context_apply(tmp_20k_buf, fs->context, folder); - if(!mail_unsubscribe(NULL, tmp_20k_buf)){ - q_status_message1(SM_ORDER | SM_DING, 3, 3, -- "Error unsubscribing from \"%s\"", folder); -+ "自 \"%s\" 取消訂閱\時發生錯誤", folder); - return(0); - } - -@@ -4366,7 +4369,7 @@ - - if(!folder_total(FOLDERS(fs->context))){ - q_status_message(SM_ORDER | SM_DING, 0, 4, -- "Empty folder collection. No folder to delete!"); -+ "空的資料匣總集。沒有東西可供刪除!"); - return(0); - } - -@@ -4376,12 +4379,12 @@ - - if(ps_global->readonly_pinerc && (fs->context->use & CNTXT_INCMNG)){ - q_status_message(SM_ORDER,3,5, -- "Deletion cancelled: config file not editable"); -+ "取消刪除:無法編輯設定檔"); - return(0); - } - else if(strucmp(folder, ps_global->inbox_name) == 0 || fp->parent) { - q_status_message1(SM_ORDER | SM_DING, 3, 4, -- "Can't delete special folder \"%s\".", ps_global->inbox_name); -+ "無法刪除特殊資料匣 \"%s\"。", ps_global->inbox_name); - return(0); - } - else if(fs->context == ps_global->context_current -@@ -4396,7 +4399,7 @@ - - if(ret){ - q_status_message1(SM_ORDER | SM_DING, 3, 4, -- "Can't delete non-empty directory \"%s\".", -+ "無法刪除非空的目錄 \"%s\"。", - folder); - return(0); - } -@@ -4407,19 +4410,19 @@ - */ - if(folder_index(folder, fs->context, FI_FOLDER) >= 0 - && (ret = want_to(DIR_FOLD_PMT,'n','x',NO_HELP,WT_NORM)) != 'y'){ -- q_status_message(SM_ORDER,0,3, (ret == 'x') ? "Delete cancelled" -- : "No folder deleted"); -+ q_status_message(SM_ORDER,0,3, (ret == 'x') ? "取消刪除" -+ : "沒有任何資料匣被刪除"); - return(0); - } - } - - if(fs->context->use & CNTXT_INCMNG){ - static ESCKEY_S delf_opts[] = { -- {'n', 'n', "N", "Nickname only"}, -- {'b', 'b', "B", "Both Folder and Nickname"}, -+ {'n', 'n', "N", "僅有暱稱"}, -+ {'b', 'b', "B", "資料匣與暱稱"}, - {-1, 0, NULL, NULL} - }; --#define DELF_PROMPT "DELETE only Nickname or Both nickname and folder? " -+#define DELF_PROMPT "刪除「僅有暱稱」或「資料匣與暱稱」?" - - switch(radio_buttons(DELF_PROMPT, -FOOTER_ROWS(ps_global), - delf_opts,'n','x',NO_HELP,RB_NORM)){ -@@ -4436,13 +4439,13 @@ - } - } - else{ -- sprintf(ques_buf, "DELETE \"%s\"%s", folder, -- close_opened ? " (the currently open folder)" -- : fp->isdir ? " (a directory)" : ""); -+ sprintf(ques_buf, "刪除 \"%s\"%s", folder, -+ close_opened ? " (目前開啟的資料匣)" -+ : fp->isdir ? " (目錄)" : ""); - - if((ret = want_to(ques_buf, 'n', 'x', NO_HELP, WT_NORM)) != 'y'){ -- q_status_message(SM_ORDER,0,3, (ret == 'x') ? "Delete cancelled" -- : "No folder deleted"); -+ q_status_message(SM_ORDER,0,3, (ret == 'x') ? "取消刪除" -+ : "沒有任何資料匣被刪除"); - return(0); - } - } -@@ -4485,13 +4488,13 @@ - /* - * BUG: what if sent-mail or saved-messages???? - */ -- q_status_message1(SM_ORDER,3,3,"Delete of \"%s\" Failed!", folder); -+ q_status_message1(SM_ORDER,3,3,"刪除 \"%s\" 失敗!", folder); - return(0); - } - } - -- q_status_message2(SM_ORDER, 0, 3, "%s \"%s\" deleted!", -- blast_folder ? "Folder" : "Nickname", folder); -+ q_status_message2(SM_ORDER, 0, 3, "%s \"%s\" 已被刪除!", -+ blast_folder ? "資料匣" : "暱稱", folder); - - - if(fs->context->use & CNTXT_INCMNG){ -@@ -4636,7 +4639,7 @@ - int flags; - - pat[0] = '\0'; -- sprintf(prompt, "String in folder %s to match : ", kind); -+ sprintf(prompt, "資料匣 %s 中欲符合的字串:", kind); - - while(1){ - flags = OE_APPEND_CURRENT | OE_DISALLOW_HELP; -@@ -4761,7 +4764,7 @@ - if(!strucmp(folder = f->name, ps_global->inbox_name)) - return(FEX_ISFILE); - -- sprintf(tmp, "Scanning \"%.*s\"", 40, FLDR_NAME(f)); -+ sprintf(tmp, "正在掃描 \"%.*s\"", 40, FLDR_NAME(f)); - we_cancel = busy_alarm(1, tmp, NULL, 0); - - mm_list_info = &ldata; /* tie down global reference */ -@@ -4926,7 +4929,7 @@ - while(1){ - flags = OE_APPEND_CURRENT | OE_DISALLOW_HELP; - sprintf(number, "%ld", *count); -- sprintf(prompt, "Select folders with messages %s : ", tense[*cmp]); -+ sprintf(prompt, "選擇有信件 %s 的資料匣:", tense[*cmp]); - r = optionally_enter(number, -FOOTER_ROWS(ps_global), 0, 31, - prompt, sel_num_opt, NO_HELP, &flags); - switch (r){ -@@ -4935,7 +4938,7 @@ - break; - else if((*count = atol(number)) < 0L) - q_status_message(SM_ORDER, 3, 3, -- "Can't have NEGATIVE message count!"); -+ "不可有「負的」信件數量!"); - else - return(1); /* success */ - -@@ -5141,14 +5144,14 @@ - int rc, t_index, done = 0; - static char search_string[MAX_SEARCH+1]; - static ESCKEY_S search_keys[] = {{0, 0, NULL, NULL}, -- {ctrl('Y'), 10, "^Y","First Fldr"}, -- {ctrl('V'), 11, "^V","Last Fldr"}, -+ {ctrl('Y'), 10, "^Y","第一個資料匣"}, -+ {ctrl('V'), 11, "^V","最後一個資料匣"}, - {-1, 0, NULL, NULL} }; - - nsearch_string[0] = '\0'; - if(!folder_total(FOLDERS(fd->context))){ - q_status_message(SM_ORDER | SM_DING, 0, 4, -- "Empty folder collection. No folders to search!"); -+ "空的資料匣總集。沒有東西可供搜尋!"); - return(0); - } - else{ -@@ -5156,7 +5159,7 @@ - search_keys[0].ch = ctrl('X'); - search_keys[0].rval = 9; - search_keys[0].name = "^X"; -- search_keys[0].label = "List Matches"; -+ search_keys[0].label = "列出符合者"; - } - else{ - search_keys[0].ch = 0; -@@ -5167,7 +5170,7 @@ - } - - t_index = fd->folder_index; -- sprintf(prompt, "Folder name to search for %s%s%s: ", -+ sprintf(prompt, "欲搜尋的資料匣名稱 %s%s%s:", - (*search_string == '\0') ? "" : "[", - search_string, - (*search_string == '\0') ? "" : "] "); -@@ -5178,7 +5181,7 @@ - switch(optionally_enter(nsearch_string, ask_line, 0, MAX_SEARCH, - prompt, search_keys, help, &flags)){ - case -1 : -- q_status_message(SM_ORDER | SM_DING, 3, 3, "Error reading word"); -+ q_status_message(SM_ORDER | SM_DING, 3, 3, "讀取字元時發生錯誤"); - return(0); - - case 0 : /*----- Search away ------*/ -@@ -5235,9 +5238,9 @@ - } - - if(rc){ -- q_status_message2(SM_ORDER, 0, 3, -- "Searched matched %s folder%s", -- int2string(count), plural(count)); -+ q_status_message1(SM_ORDER, 0, 3, -+ "共有 %s 個資料匣符合搜尋條件", -+ int2string(count)); - fd->prev_index = -1; /* repaint! */ - } - -@@ -5251,14 +5254,14 @@ - while((t_index = folder_lister_prev(fd)) >= 0) - fd->folder_index = t_index; - -- q_status_message(SM_ORDER, 0, 3, "Searched to First Folder."); -+ q_status_message(SM_ORDER, 0, 3, "搜尋至第一個資料匣。"); - return(3); - - case 11 : - while((t_index = folder_lister_next(fd))) - fd->folder_index = t_index; - -- q_status_message(SM_ORDER, 0, 3, "Searched to Last Folder."); -+ q_status_message(SM_ORDER, 0, 3, "搜尋至最後一個資料匣。"); - return(3); - - default : -@@ -6251,7 +6254,7 @@ - - if(p = context_digest(c_string, dcontext, host, rcontext, view)){ - q_status_message2(SM_ORDER | SM_DING, 3, 4, -- "Bad context, %s : %s", p, c_string); -+ "錯誤的內容,%s:%s", p, c_string); - fs_give((void **) &c_string); - if(nickname) - fs_give((void **)&nickname); -@@ -6292,14 +6295,14 @@ - - /* fix up label */ - if(NEWS_TEST(c)){ -- sprintf(tmp_20k_buf, "%sews groups%s%s", -- (*host) ? "N" : "Local n", (*host) ? " on " : "", -+ sprintf(tmp_20k_buf, "%s文組群%s%s", -+ (*host) ? "新" : "本地的新", (*host) ? " 於 " : "", - (*host) ? host : ""); - } - else{ - p = srchstr(rcontext, "[]"); -- sprintf(tmp_20k_buf, "%solders%s%s in %.*s%s", -- (*host) ? "F" : "Local f", (*host) ? " on " : "", -+ sprintf(tmp_20k_buf, "%s料匣%s%s在 %.*s%s", -+ (*host) ? "資" : "本地的資", (*host) ? " 於 " : "", - (*host) ? host : "", p ? p - rcontext : 0, - rcontext, (p && (p - rcontext) > 0) ? "" : "home directory"); - } -@@ -7482,7 +7485,7 @@ - if(error && num_in_error){ - cnt_errs = num_in_error; - memset((void *)ng_error, 0, (size_t)90); -- sprintf(ng_error, "Unknown news group%s: ", plural(num_in_error)); -+ sprintf(ng_error, "未知的新聞組群:"); - ep = ng_error + strlen(ng_error); - } - for(ntmp = nglist; ntmp; ntmp = ntmp->next){ ++ _col++; ++ ++ break; ++ } ++ } ++ ++ ++ /* Here we are at the end of the line. We've decided to make no ++ assumptions about how the terminal behaves at this point. ++ What can happen now are the following ++ 1. Cursor is at start of next line, and next character will ++ apear there. (autowrap, !newline glitch) ++ 2. Cursor is at start of next line, and if a newline is output ++ it'll be ignored. (autowrap, newline glitch) ++ 3. Cursor is still at end of line and next char will apear ++ there over the top of what is there now (no autowrap). ++ We ignore all this and force the cursor to the next line, just ++ like case 1. A little expensive but worth it to avoid problems ++ with terminals configured so they don't match termcap ++ */ ++ if(_col == ps_global->ttyo->screen_cols) { ++ _col = 0; ++ if(_line + 1 < ps_global->ttyo->screen_rows) ++ _line++; ++ ++ moveabsolute(_col, _line); ++ } ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Write string to screen at current cursor position ++ ++ Args: string -- strings to be output ++ ++ Result: Line written to the screen ++ ----*/ ++void ++Write_to_screen(string) /* UNIX */ ++ register char *string; ++{ ++ while(*string) ++ Writechar((unsigned char) *string++, 0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Clear screen to end of line on current line ++ ++ Result: Line is cleared ++ ----*/ ++void ++CleartoEOLN() ++{ ++ if(!_cleartoeoln) ++ return; ++ ++ tputs(_cleartoeoln, 1, outchar); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Clear screen to end of screen from current point ++ ++ Result: screen is cleared ++ ----*/ ++CleartoEOS() ++{ ++ if(!_cleartoeos){ ++ CleartoEOLN(); ++ ClearLines(_line, _lines-1); ++ } ++ else ++ tputs(_cleartoeos, 1, outchar); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ function to output character used by termcap ++ ++ Args: c -- character to output ++ ++ Result: character output to screen via stdio ++ ----*/ ++void ++outchar(c) ++int c; ++{ ++ /** output the given character. From tputs... **/ ++ /** Note: this CANNOT be a macro! **/ ++ ++ putc((unsigned char)c, stdout); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ function to output string such that it becomes icon text ++ ++ Args: s -- string to write ++ ++ Result: string indicated become our "icon" text ++ ----*/ ++void ++icon_text(s) ++ char *s; ++{ ++ static char *old_s; ++ static enum {ukn, yes, no} xterm; ++ ++ if(xterm == ukn) ++ xterm = (getenv("DISPLAY") != NULL) ? yes : no; ++ ++ if(F_ON(F_ENABLE_XTERM_NEWMAIL,ps_global) && xterm == yes && (s || old_s)){ ++ fputs("\033]1;", stdout); ++ fputs((old_s = s) ? s : ps_global->pine_name, stdout); ++ fputs("\007", stdout); ++ fflush(stdout); ++ } ++} ++ ++ ++#ifdef _WINDOWS ++#line 3 "osdep/termout.gen" ++#endif ++ ++/* ++ * Generic tty output routines... ++ */ ++ ++/*---------------------------------------------------------------------- ++ Printf style output line to the screen at given position, 0 args ++ ++ Args: x -- column position on the screen ++ y -- row position on the screen ++ line -- line of text to output ++ ++ Result: text is output ++ cursor position is update ++ ----*/ ++void ++PutLine0(x, y, line) ++ int x,y; ++ register char *line; ++{ ++ MoveCursor(x,y); ++ Write_to_screen(line); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Output line of length len to the display observing embedded attributes ++ ++ Args: x -- column position on the screen ++ y -- column position on the screen ++ line -- text to be output ++ length -- length of text to be output ++ ++ Result: text is output ++ cursor position is updated ++ ----------------------------------------------------------------------*/ ++void ++PutLine0n8b(x, y, line, length, handles) ++ int x, y, length; ++ register char *line; ++ HANDLE_S *handles; ++{ ++ unsigned char c; ++ ++ MoveCursor(x,y); ++ while(length-- && (c = (unsigned char)*line++)){ ++ if(c == (unsigned char)TAG_EMBED && length){ ++ length--; ++ switch(*line++){ ++ case TAG_INVON : ++ StartInverse(); ++ break; ++ case TAG_INVOFF : ++ EndInverse(); ++ break; ++ case TAG_BOLDON : ++ StartBold(); ++ break; ++ case TAG_BOLDOFF : ++ EndBold(); ++ break; ++ case TAG_ULINEON : ++ StartUnderline(); ++ break; ++ case TAG_ULINEOFF : ++ EndUnderline(); ++ break; ++ case TAG_HANDLE : ++ length -= *line + 1; /* key length plus length tag */ ++ if(handles){ ++ int key, n; ++ ++ for(key = 0, n = *line++; n; n--) /* forget Horner? */ ++ key = (key * 10) + (*line++ - '0'); ++ ++ if(key == handles->key){ ++ EndBold(); ++ StartInverse(); ++ } ++ } ++ else{ ++ /* BUG: complain? */ ++ line += *line + 1; ++ } ++ ++ break; ++ default : /* literal "embed" char? */ ++ Writechar(TAG_EMBED, 0); ++ Writechar(*(line-1), 0); ++ break; ++ } /* tag with handle, skip it */ ++ } ++ else if(c == '\033') /* check for iso-2022 escape */ ++ Writechar(c, match_escapes(line)); ++ else ++ Writechar(c, 0); ++ } ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Printf style output line to the screen at given position, 1 arg ++ ++ Input: position on the screen ++ line of text to output ++ ++ Result: text is output ++ cursor position is update ++ ----------------------------------------------------------------------*/ ++void ++/*VARARGS2*/ ++PutLine1(x, y, line, arg1) ++ int x, y; ++ char *line; ++ void *arg1; ++{ ++ char buffer[PUTLINE_BUFLEN]; ++ ++ sprintf(buffer, line, arg1); ++ PutLine0(x, y, buffer); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Printf style output line to the screen at given position, 2 args ++ ++ Input: position on the screen ++ line of text to output ++ ++ Result: text is output ++ cursor position is update ++ ----------------------------------------------------------------------*/ ++void ++/*VARARGS3*/ ++PutLine2(x, y, line, arg1, arg2) ++ int x, y; ++ char *line; ++ void *arg1, *arg2; ++{ ++ char buffer[PUTLINE_BUFLEN]; ++ ++ sprintf(buffer, line, arg1, arg2); ++ PutLine0(x, y, buffer); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Printf style output line to the screen at given position, 3 args ++ ++ Input: position on the screen ++ line of text to output ++ ++ Result: text is output ++ cursor position is update ++ ----------------------------------------------------------------------*/ ++void ++/*VARARGS4*/ ++PutLine3(x, y, line, arg1, arg2, arg3) ++ int x, y; ++ char *line; ++ void *arg1, *arg2, *arg3; ++{ ++ char buffer[PUTLINE_BUFLEN]; ++ ++ sprintf(buffer, line, arg1, arg2, arg3); ++ PutLine0(x, y, buffer); ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Printf style output line to the screen at given position, 4 args ++ ++ Args: x -- column position on the screen ++ y -- column position on the screen ++ line -- printf style line of text to output ++ ++ Result: text is output ++ cursor position is update ++ ----------------------------------------------------------------------*/ ++void ++/*VARARGS5*/ ++PutLine4(x, y, line, arg1, arg2, arg3, arg4) ++ int x, y; ++ char *line; ++ void *arg1, *arg2, *arg3, *arg4; ++{ ++ char buffer[PUTLINE_BUFLEN]; ++ ++ sprintf(buffer, line, arg1, arg2, arg3, arg4); ++ PutLine0(x, y, buffer); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Printf style output line to the screen at given position, 5 args ++ ++ Args: x -- column position on the screen ++ y -- column position on the screen ++ line -- printf style line of text to output ++ ++ Result: text is output ++ cursor position is update ++ ----------------------------------------------------------------------*/ ++void ++/*VARARGS6*/ ++PutLine5(x, y, line, arg1, arg2, arg3, arg4, arg5) ++ int x, y; ++ char *line; ++ void *arg1, *arg2, *arg3, *arg4, *arg5; ++{ ++ char buffer[PUTLINE_BUFLEN]; ++ ++ sprintf(buffer, line, arg1, arg2, arg3, arg4, arg5); ++ PutLine0(x, y, buffer); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Output a line to the screen, centered ++ ++ Input: Line number to print on, string to output ++ ++ Result: String is output to screen ++ Returns column number line is output on ++ ----------------------------------------------------------------------*/ ++int ++Centerline(line, string) ++ int line; ++ char *string; ++{ ++ register int length, col; ++ ++ length = strlen(string); ++ ++ if (length > ps_global->ttyo->screen_cols) ++ col = 0; ++ else ++ col = (ps_global->ttyo->screen_cols - length) / 2; ++ ++ PutLine0(line, col, string); ++ return(col); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Clear specified line on the screen ++ ++ Result: The line is blanked and the cursor is left at column 0. ++ ++ ----*/ ++void ++ClearLine(n) ++ int n; ++{ ++ if(ps_global->in_init_seq) ++ return; ++ ++ MoveCursor(n, 0); ++ CleartoEOLN(); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Clear specified lines on the screen ++ ++ Result: The lines starting at 'x' and ending at 'y' are blanked ++ and the cursor is left at row 'x', column 0 ++ ++ ----*/ ++void ++ClearLines(x, y) ++ int x, y; ++{ ++ int i; ++ ++ for(i = x; i <= y; i++) ++ ClearLine(i); ++ ++ MoveCursor(x, 0); ++} ++ ++ ++ ++/*---------------------------------------------------------------------- ++ Indicate to the screen painting here that the position of the cursor ++ has been disturbed and isn't where these functions might think. ++ ----*/ ++void ++clear_cursor_pos() ++{ ++ _line = FARAWAY; ++ _col = FARAWAY; ++} ++ ++ ++/*---------------------------------------------------------------------- ++ Return current inverse state ++ ++ Result: returns 1 if in inverse state, 0 if not. ++ ----------------------------------------------------------------------*/ ++int ++InverseState() ++{ ++ return(_in_inverse); ++} ++ ++ |