4 * This file contains functions which implement parts of the
5 * text-mode user interface.
16 #include <sys/types.h>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
35 #ifdef HAVE_SYS_SELECT_H
36 #include <sys/select.h>
39 #ifdef THREADED_CLIENT
49 #include "citadel_decls.h"
51 #include "routines2.h"
54 #include "client_chat.h"
67 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
71 char rc_exp_cmd[1024];
72 int rc_allow_attachments;
73 int rc_display_message_numbers;
74 int rc_force_mail_prompts;
75 int rc_remember_passwords;
78 int rc_prompt_control = 0;
79 char urls[MAXURLS][SIZ];
83 int next_lazy_cmd = 5;
85 int lines_printed = 0; /* line count for paginator */
86 extern int screenwidth, screenheight;
89 struct citcmd *cmdlist = NULL;
92 /* these variables are local to this module */
93 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
94 int ok_to_interrupt = 0; /* print express msgs asynchronously */
95 time_t AnsiDetect; /* when did we send the detect code? */
96 int enable_color = 0; /* nonzero for ANSI color */
102 * If an interesting key has been pressed, return its value, otherwise 0
104 char was_a_key_pressed(void) {
114 retval = select(1, &rfds, NULL, NULL, &tv);
116 /* Careful! Disable keepalives during keyboard polling; we're probably
117 * in the middle of a data transfer from the server, in which case
118 * sending a NOOP would throw the client protocol out of sync.
120 if (FD_ISSET(0, &rfds)) {
121 set_keepalives(KA_NO);
122 the_character = inkey();
123 set_keepalives(KA_YES);
128 return(the_character);
136 * Check to see if we need to pause at the end of a screen.
137 * If we do, we have to disable server keepalives during the pause because
138 * we are probably in the middle of a server operation and the NOOP command
139 * would confuse everything.
141 int checkpagin(int lp, int pagin, int height)
145 if (sigcaught) return(lp);
146 thekey = was_a_key_pressed();
147 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
149 if (thekey == 'n' || thekey == 'N')
151 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
152 if (sigcaught) return(lp);
154 if (!pagin) return(0);
155 if (lp>=(height-1)) {
156 set_keepalives(KA_NO);
158 set_keepalives(KA_YES);
168 * pprintf() ... paginated version of printf()
170 void pprintf(const char *format, ...) {
172 static char buf[4096]; /* static for performance, change if needed */
175 /* If sigcaught is nonzero, a keypress has interrupted this and we
176 * should just drain output.
178 if (sigcaught) return;
180 /* Otherwise, start spewing... */
181 va_start(arg_ptr, format);
182 vsnprintf(buf, sizeof(buf), format, arg_ptr);
185 for (i=0; i<strlen(buf); ++i) {
189 lines_printed = checkpagin(lines_printed,
190 (userflags & US_PAGINATOR),
199 * print_express() - print express messages if there are any
201 void print_express(void)
211 if (express_msgs == 0)
217 if (strlen(rc_exp_cmd) == 0) {
222 while (express_msgs != 0) {
228 express_msgs = extract_int(&buf[4], 0);
229 timestamp = extract_long(&buf[4], 1);
230 flags = extract_int(&buf[4], 2);
231 extract(sender, &buf[4], 3);
232 extract(node, &buf[4], 4);
233 strcpy(last_paged, sender);
235 stamp = localtime(×tamp);
237 /* If the page is a Logoff Request, honor it. */
243 if (strlen(rc_exp_cmd) > 0) {
244 outpipe = popen(rc_exp_cmd, "w");
245 if (outpipe != NULL) {
246 /* Header derived from flags */
249 "Please log off now, as requested ");
251 fprintf(outpipe, "Broadcast message ");
253 fprintf(outpipe, "Chat request ");
255 fprintf(outpipe, "Message ");
256 /* Timestamp. Can this be improved? */
257 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)
258 fprintf(outpipe, "at 12:%02d%cm",
260 stamp->tm_hour ? 'p' : 'a');
261 else if (stamp->tm_hour > 12) /* pm */
262 fprintf(outpipe, "at %d:%02dpm",
266 fprintf(outpipe, "at %d:%02dam",
267 stamp->tm_hour, stamp->tm_min);
268 fprintf(outpipe, " from %s", sender);
269 if (strncmp(serv_info.serv_nodename, node, 32))
270 fprintf(outpipe, " @%s", node);
271 fprintf(outpipe, ":\n");
272 while (serv_gets(buf), strcmp(buf, "000")) {
273 fprintf(outpipe, "%s\n", buf);
276 if (express_msgs == 0)
281 /* fall back to built-in express message display */
285 /* Header derived from flags */
287 scr_printf("Please log off now, as requested ");
289 scr_printf("Broadcast message ");
291 scr_printf("Chat request ");
293 scr_printf("Message ");
295 /* Timestamp. Can this be improved? */
296 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)/* 12am/12pm */
297 scr_printf("at 12:%02d%cm", stamp->tm_min,
298 stamp->tm_hour ? 'p' : 'a');
299 else if (stamp->tm_hour > 12) /* pm */
300 scr_printf("at %d:%02dpm",
301 stamp->tm_hour - 12, stamp->tm_min);
303 scr_printf("at %d:%02dam", stamp->tm_hour, stamp->tm_min);
306 scr_printf(" from %s", sender);
308 /* Remote node, if any */
309 if (strncmp(serv_info.serv_nodename, node, 32))
310 scr_printf(" @%s", node);
314 fmout(screenwidth, NULL, NULL, 1, screenheight, -1, 0);
316 /* when running in curses mode, the scroll bar in most
317 xterm-style programs becomes useless, so it makes sense to
318 pause after a screenful of pages if the user has been idle
319 for a while. However, this is annoying to some of the users
320 who aren't in curses mode and tend to leave their clients
321 idle. keepalives become disabled, resulting in getting booted
322 when coming back to the idle session. but they probably have
323 a working scrollback in their terminal, so disable it in this
326 if (!is_curses_enabled())
329 scr_printf("\n---\n");
336 void set_keepalives(int s)
338 keepalives_enabled = (char) s;
342 * This loop handles the "keepalive" messages sent to the server when idling.
345 static time_t idlet = 0;
346 static void really_do_keepalive(void) {
350 if (keepalives_enabled == KA_YES) {
355 if (ok_to_interrupt == 1) {
356 scr_printf("\r%64s\r", "");
358 scr_printf("%s%c ", room_name,
359 room_prompt(room_flags));
366 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
367 encapsulated interface; in theory there should be no need to touch these
368 globals outside of the async_ka_* functions. */
370 #ifdef THREADED_CLIENT
371 static pthread_t ka_thr_handle;
372 static int ka_thr_active = 0;
373 static int async_ka_enabled = 0;
375 static void *ka_thread(void *arg)
377 really_do_keepalive();
378 pthread_detach(ka_thr_handle);
383 /* start up a thread to handle a keepalive in the background */
384 static void async_ka_exec(void)
386 if (!ka_thr_active) {
388 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
389 perror("pthread_create");
394 #endif /* THREADED_CLIENT */
396 /* I changed this from static to not because I need to call it from
397 screen.c, either that or make something in screen.c not static.
398 Fix it how you like. Why all the staticness? stu */
400 void do_keepalive(void)
405 if ((now - idlet) < ((long) S_KEEPALIVE))
408 /* Do a space-backspace to keep telnet sessions from idling out */
409 scr_printf(" %c", 8);
412 #ifdef THREADED_CLIENT
413 if (async_ka_enabled)
417 really_do_keepalive();
421 /* Now the actual async-keepalve API that we expose to higher levels:
422 async_ka_start() and async_ka_end(). These do nothing when we don't have
423 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
425 /* wait for a background keepalive to complete. this must be done before
426 attempting any further server requests! */
427 void async_ka_end(void)
429 #ifdef THREADED_CLIENT
431 pthread_join(ka_thr_handle, NULL);
437 /* tell do_keepalive() that keepalives are asynchronous. */
438 void async_ka_start(void)
440 #ifdef THREADED_CLIENT
447 { /* get a character from the keyboard, with */
448 int a; /* the watchdog timer in effect if necessary */
458 /* This loop waits for keyboard input. If the keepalive
459 * timer expires, it sends a keepalive to the server if
460 * necessary and then waits again.
467 tv.tv_sec = S_KEEPALIVE;
470 select(1, &rfds, NULL, NULL, &tv);
471 } while (!FD_ISSET(0, &rfds));
473 /* At this point, there's input, so fetch it.
474 * (There's a hole in the bucket...)
483 if (((a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
484 && ((a < 32) || (a > 126)))
492 { /* Returns 1 for yes, 0 for no */
508 /* Returns 1 for yes, 0 for no, arg is default value */
531 /* Gets a line from the terminal */
532 /* string == Pointer to string buffer */
533 /* lim == Maximum length - if negative, no-show */
534 void getline(char *string, int lim)
548 if ((a == 8) && (strlen(string) == 0))
550 if ((a != 10) && (a != 8) && (strlen(string) == lim))
552 if ((a == 8) && (string[0] != 0)) {
553 string[strlen(string) - 1] = 0;
578 * strprompt() - prompt for a string, print the existing value and
579 * allow the user to press return to keep it...
581 void strprompt(char *prompt, char *str, int len)
588 scr_printf("%s ", prompt);
591 color(BRIGHT_MAGENTA);
594 scr_printf("%s", str);
597 for (i=0; i<strlen(str); ++i) {
614 * boolprompt() - prompt for a yes/no, print the existing value and
615 * allow the user to press return to keep it...
617 int boolprompt(char *prompt, int prev_val)
622 scr_printf("%s ", prompt);
625 color(BRIGHT_MAGENTA);
626 scr_printf("%s", (prev_val ? "Yes" : "No"));
630 r = (yesno_d(prev_val));
636 * intprompt() - like strprompt(), except for an integer
637 * (note that it RETURNS the new value!)
639 int intprompt(char *prompt, int ival, int imin, int imax)
647 snprintf(buf, sizeof buf, "%d", i);
648 strprompt(prompt, buf, 15);
650 for (p=0; p<strlen(buf); ++p) {
651 if ( (!isdigit(buf[p]))
652 && ( (buf[p]!='-') || (p!=0) ) )
656 scr_printf("*** Must be no less than %d.\n", imin);
658 scr_printf("*** Must be no more than %d.\n", imax);
659 } while ((i < imin) || (i > imax));
664 * newprompt() - prompt for a string with no existing value
665 * (clears out string buffer first)
667 void newprompt(char *prompt, char *str, int len)
669 color(BRIGHT_MAGENTA);
670 scr_printf("%s", prompt);
678 { /* returns a lower case value */
687 * parse the citadel.rc file
689 void load_command_set(void)
694 struct citcmd *lastcmd = NULL;
699 /* first, set up some defaults for non-required variables */
701 strcpy(editor_path, "");
702 strcpy(printcmd, "");
703 strcpy(rc_username, "");
704 strcpy(rc_password, "");
707 rc_allow_attachments = 0;
708 rc_remember_passwords = 0;
709 strcpy(rc_exp_cmd, "");
710 rc_display_message_numbers = 0;
711 rc_force_mail_prompts = 0;
713 strcpy(rc_url_cmd, "");
715 rc_encrypt = RC_DEFAULT;
718 rc_screen = RC_DEFAULT;
720 rc_alt_semantics = 0;
722 /* now try to open the citadel.rc file */
725 if (getenv("HOME") != NULL) {
726 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
727 ccfile = fopen(buf, "r");
729 if (ccfile == NULL) {
730 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
731 ccfile = fopen(buf, "r");
733 if (ccfile == NULL) {
734 ccfile = fopen("/etc/citadel.rc", "r");
736 if (ccfile == NULL) {
737 ccfile = fopen("./citadel.rc", "r");
739 if (ccfile == NULL) {
740 perror("commands: cannot open citadel.rc");
743 while (fgets(buf, sizeof buf, ccfile) != NULL) {
744 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
745 buf[strlen(buf) - 1] = 0;
747 if (!strncasecmp(buf, "encrypt=", 8)) {
748 if (!strcasecmp(&buf[8], "yes")) {
752 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
757 else if (!strcasecmp(&buf[8], "no")) {
760 else if (!strcasecmp(&buf[8], "default")) {
761 rc_encrypt = RC_DEFAULT;
767 if (!strncasecmp(buf, "fullscreen=", 11)) {
768 if (!strcasecmp(&buf[11], "yes"))
770 else if (!strcasecmp(&buf[11], "no"))
775 if (!strncasecmp(buf, "editor=", 7))
776 strcpy(editor_path, &buf[7]);
778 if (!strncasecmp(buf, "printcmd=", 9))
779 strcpy(printcmd, &buf[9]);
781 if (!strncasecmp(buf, "expcmd=", 7))
782 strcpy(rc_exp_cmd, &buf[7]);
784 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
785 have_xterm = (char) atoi(&buf[24]);
787 if (!strncasecmp(buf, "use_floors=", 11)) {
788 if (!strcasecmp(&buf[11], "yes"))
789 rc_floor_mode = RC_YES;
790 if (!strcasecmp(&buf[11], "no"))
791 rc_floor_mode = RC_NO;
792 if (!strcasecmp(&buf[11], "default"))
793 rc_floor_mode = RC_DEFAULT;
795 if (!strncasecmp(buf, "beep=", 5)) {
796 rc_exp_beep = atoi(&buf[5]);
798 if (!strncasecmp(buf, "allow_attachments=", 18)) {
799 rc_allow_attachments = atoi(&buf[18]);
801 if (!strncasecmp(buf, "remember_passwords=", 19)) {
802 rc_remember_passwords = atoi(&buf[19]);
804 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
805 rc_display_message_numbers = atoi(&buf[24]);
807 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
808 rc_force_mail_prompts = atoi(&buf[19]);
810 if (!strncasecmp(buf, "ansi_color=", 11)) {
811 if (!strncasecmp(&buf[11], "on", 2))
813 if (!strncasecmp(&buf[11], "auto", 4))
814 rc_ansi_color = 2; /* autodetect */
815 if (!strncasecmp(&buf[11], "user", 4))
816 rc_ansi_color = 3; /* user config */
818 if (!strncasecmp(buf, "prompt_control=", 15)) {
819 if (!strncasecmp(&buf[15], "on", 2))
820 rc_prompt_control = 1;
821 if (!strncasecmp(&buf[15], "user", 4))
822 rc_prompt_control = 3; /* user config */
824 if (!strncasecmp(buf, "username=", 9))
825 strcpy(rc_username, &buf[9]);
827 if (!strncasecmp(buf, "password=", 9))
828 strcpy(rc_password, &buf[9]);
830 if (!strncasecmp(buf, "urlcmd=", 7))
831 strcpy(rc_url_cmd, &buf[7]);
833 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
834 if (!strncasecmp(&buf[11], "yes", 3))
835 rc_alt_semantics = 1;
836 if (!strncasecmp(&buf[11], "no", 2))
837 rc_alt_semantics = 0;
839 if (!strncasecmp(buf, "cmd=", 4)) {
840 strcpy(buf, &buf[4]);
842 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
844 cptr->c_cmdnum = atoi(buf);
845 for (d = strlen(buf); d >= 0; --d)
848 strcpy(buf, &buf[b + 1]);
850 cptr->c_axlevel = atoi(buf);
851 for (d = strlen(buf); d >= 0; --d)
854 strcpy(buf, &buf[b + 1]);
856 for (a = 0; a < 5; ++a)
857 cptr->c_keys[a][0] = 0;
861 buf[strlen(buf) + 1] = 0;
862 while (strlen(buf) > 0) {
864 for (d = strlen(buf); d >= 0; --d)
867 strncpy(cptr->c_keys[a], buf, b);
868 cptr->c_keys[a][b] = 0;
870 strcpy(buf, &buf[b + 1]);
880 lastcmd->next = cptr;
890 * return the key associated with a command
892 char keycmd(char *cmdstr)
896 for (a = 0; a < strlen(cmdstr); ++a)
897 if (cmdstr[a] == '&')
898 return (tolower(cmdstr[a + 1]));
904 * Output the string from a key command without the ampersand
905 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
907 char *cmd_expand(char *strbuf, int mode)
915 for (a = 0; a < strlen(exp); ++a) {
916 if (strbuf[a] == '&') {
919 strcpy(&exp[a], &exp[a + 1]);
923 strcpy(buf, &exp[a + 2]);
929 if (!strncmp(&exp[a], "^r", 2)) {
931 strcpy(&exp[a], room_name);
932 strcat(exp, &buf[a + 2]);
934 if (!strncmp(&exp[a], "^c", 2)) {
936 strcpy(&exp[a + 1], &exp[a + 2]);
946 * Comparison function to determine if entered commands match a
947 * command loaded from the config file.
949 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
960 for (a = 0; a < ncomp; ++a) {
961 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
962 || (cptr->c_axlevel > cmdax))
970 * This function returns 1 if a given command requires a string input
972 int requires_string(struct citcmd *cptr, int ncomp)
977 strcpy(buf, cptr->c_keys[ncomp - 1]);
978 for (a = 0; a < strlen(buf); ++a) {
987 * Input a command at the main prompt.
988 * This function returns an integer command number. If the command prompts
989 * for a string then it is placed in the supplied buffer.
991 int getcmd(char *argbuf)
1000 struct citcmd *cptr;
1003 * Starting a new command now, so set sigcaught to 0. This variable
1004 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1005 * been interrupted by a keypress.
1009 /* Switch color support on or off if we're in user mode */
1010 if (rc_ansi_color == 3) {
1011 if (userflags & US_COLOR)
1016 /* if we're running in idiot mode, display a cute little menu */
1017 IFNEXPERT formout("mainmenu");
1019 print_express(); /* print express messages if there are any */
1022 for (a = 0; a < 5; ++a)
1024 /* now the room prompt... */
1025 ok_to_interrupt = 1;
1026 color(BRIGHT_WHITE);
1027 scr_printf("\n%s", room_name);
1029 scr_printf("%c ", room_prompt(room_flags));
1034 ok_to_interrupt = 0;
1036 /* Handle the backspace key, but only if there's something
1037 * to backspace over...
1039 if ((ch == 8) && (cmdpos > 0)) {
1040 back(cmdspaces[cmdpos - 1] + 1);
1044 /* Spacebar invokes "lazy traversal" commands */
1045 if ((ch == 32) && (cmdpos == 0)) {
1046 this_lazy_cmd = next_lazy_cmd;
1047 if (this_lazy_cmd == 13)
1049 if (this_lazy_cmd == 5)
1051 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1052 if (cptr->c_cmdnum == this_lazy_cmd) {
1053 for (a = 0; a < 5; ++a)
1054 if (cptr->c_keys[a][0] != 0)
1055 scr_printf("%s ", cmd_expand(
1056 cptr->c_keys[a], 0));
1058 return (this_lazy_cmd);
1062 return (this_lazy_cmd);
1064 /* Otherwise, process the command */
1065 cmdbuf[cmdpos] = tolower(ch);
1067 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1068 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1070 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1071 cmdspaces[cmdpos] = strlen(
1072 cmd_expand(cptr->c_keys[cmdpos], 0));
1074 if ((cptr->c_keys[cmdpos + 1]) != 0)
1080 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1081 if (cmdmatch(cmdbuf, cptr, 5)) {
1082 /* We've found our command. */
1083 if (requires_string(cptr, cmdpos)) {
1084 getline(argbuf, 32);
1089 /* If this command is one that changes rooms,
1090 * then the next lazy-command (space bar)
1091 * should be "read new" instead of "goto"
1093 if ((cptr->c_cmdnum == 5)
1094 || (cptr->c_cmdnum == 6)
1095 || (cptr->c_cmdnum == 47)
1096 || (cptr->c_cmdnum == 52)
1097 || (cptr->c_cmdnum == 16)
1098 || (cptr->c_cmdnum == 20))
1101 return (cptr->c_cmdnum);
1107 pprintf("\rOne of ... \n");
1108 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1109 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1110 for (a = 0; a < 5; ++a) {
1111 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1117 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1119 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1120 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1121 for (a = 0; a < cmdpos; ++a) {
1123 cmd_expand(cptr->c_keys[a], 0));
1138 * set tty modes. commands are:
1140 * 01- set to bbs mode
1141 * 2 - save current settings for later restoral
1142 * 3 - restore saved settings
1144 #ifdef HAVE_TERMIOS_H
1145 void sttybbs(int cmd)
1146 { /* SysV version of sttybbs() */
1147 struct termios live;
1148 static struct termios saved_settings;
1149 static int last_cmd = 0;
1156 if ((cmd == 0) || (cmd == 1)) {
1157 tcgetattr(0, &live);
1158 live.c_iflag = ISTRIP | IXON | IXANY;
1159 live.c_oflag = OPOST | ONLCR;
1160 live.c_lflag = ISIG | NOFLSH;
1162 live.c_cc[VINTR] = (-1);
1163 live.c_cc[VQUIT] = (-1);
1166 live.c_cc[VMIN] = 0;
1167 live.c_cc[VTIME] = 0;
1170 /* do we even need this stuff anymore? */
1171 /* live.c_line=0; */
1172 live.c_cc[VERASE] = 8;
1173 live.c_cc[VKILL] = 24;
1174 live.c_cc[VEOF] = 1;
1175 live.c_cc[VEOL] = 255;
1176 live.c_cc[VEOL2] = 0;
1177 live.c_cc[VSTART] = 0;
1178 tcsetattr(0, TCSADRAIN, &live);
1181 tcgetattr(0, &saved_settings);
1184 tcsetattr(0, TCSADRAIN, &saved_settings);
1188 void sttybbs(int cmd)
1189 { /* BSD version of sttybbs() */
1191 static struct sgttyb saved_settings;
1192 static int last_cmd = 0;
1199 if ((cmd == 0) || (cmd == 1)) {
1201 live.sg_flags |= CBREAK;
1202 live.sg_flags |= CRMOD;
1203 live.sg_flags |= NL1;
1204 live.sg_flags &= ~ECHO;
1206 live.sg_flags |= NOFLSH;
1210 gtty(0, &saved_settings);
1213 stty(0, &saved_settings);
1220 * display_help() - help file viewer
1222 void display_help(char *name)
1229 * fmout() - Citadel text formatter and paginator
1232 int width, /* screen width to use */
1233 FILE *fpin, /* file to read from, or NULL to read from server */
1234 FILE *fpout, /* File to write to, or NULL to write to screen */
1235 char pagin, /* nonzero if we should use the paginator */
1236 int height, /* screen height to use */
1237 int starting_lp,/* starting value for lines_printed, -1 for global */
1238 char subst) /* nonzero if we should use hypertext mode */
1240 int a, b, c, d, old;
1246 num_urls = 0; /* Start with a clean slate of embedded URL's */
1248 if (starting_lp >= 0) {
1249 lines_printed = starting_lp;
1254 c = 1; /* c is the current pos */
1256 FMTA: while ((eof_flag == 0) && (strlen(buffer) < 126)) {
1257 if (fpin != NULL) { /* read from file */
1260 if (eof_flag == 0) {
1262 buffer[strlen(buffer) + 1] = 0;
1263 buffer[strlen(buffer)] = a;
1265 } else { /* read from server */
1267 serv_gets(&buffer[d]);
1268 while ((!isspace(buffer[d])) && (isspace(buffer[strlen(buffer) - 1])))
1269 buffer[strlen(buffer) - 1] = 0;
1270 if (!strcmp(&buffer[d], "000")) {
1273 while (isspace(buffer[strlen(buffer) - 1]))
1274 buffer[strlen(buffer) - 1] = 0;
1282 if ( (!strncasecmp(buffer, "http://", 7))
1283 || (!strncasecmp(buffer, "ftp://", 6)) ) {
1284 safestrncpy(urls[num_urls], buffer, (SIZ-1));
1285 for (a=0; a<strlen(urls[num_urls]); ++a) {
1286 b = urls[num_urls][a];
1287 if ( (b==' ') || (b==')') || (b=='>') || (b==10)
1288 || (b==13) || (b==9) || (b=='\"') )
1289 urls[num_urls][a] = 0;
1294 buffer[strlen(buffer) + 1] = 0;
1296 strcpy(buffer, &buffer[1]);
1303 if (((a == 13) || (a == 10)) && (old != 13) && (old != 10))
1305 if (((old == 13) || (old == 10)) && (isspace(real))) {
1307 fprintf(fpout, "\n");
1311 lines_printed = checkpagin(lines_printed, pagin, height);
1319 if (((strlen(aaa) + c) > (width - 1)) && (strlen(aaa) > (width - 1))) {
1321 fprintf(fpout, "\n%s", aaa);
1323 scr_printf("\n%s", aaa);
1325 lines_printed = checkpagin(lines_printed, pagin, height);
1335 if ((strlen(aaa) + c) > (width - 1)) {
1338 fprintf(fpout, "\n");
1342 lines_printed = checkpagin(lines_printed, pagin, height);
1346 fprintf(fpout, "%s ", aaa);
1348 scr_printf("%s ", aaa);
1351 c = c + strlen(aaa);
1355 if ((a == 13) || (a == 10)) {
1357 fprintf(fpout, "%s\n", aaa);
1359 scr_printf("%s\n", aaa);
1361 lines_printed = checkpagin(lines_printed, pagin, height);
1364 if (sigcaught) goto OOPS;
1370 /* keypress caught; drain the server */
1373 } while (strcmp(aaa, "000"));
1377 fprintf(fpout, "\n");
1381 lines_printed = checkpagin(lines_printed, pagin, height);
1388 * support ANSI color if defined
1390 void color(int colornum)
1392 static int is_bold = 0;
1393 static int hold_color, current_color;
1395 if (colornum == COLOR_PUSH) {
1396 hold_color = current_color;
1400 if (colornum == COLOR_POP) {
1405 current_color = colornum;
1407 #ifdef HAVE_CURSES_H
1408 if (scr_color(colornum))
1411 /* When switching to dim white, actually output an 'original
1412 * pair' sequence -- this looks better on black-on-white
1415 if (colornum == DIM_WHITE)
1416 printf("\033[39;49m");
1418 printf("\033[3%d;40m", (colornum & 7));
1420 if ((colornum >= 8) && (is_bold == 0)) {
1423 } else if ((colornum < 8) && (is_bold == 1)) {
1431 void cls(int colornum)
1434 printf("\033[4%dm\033[2J\033[H\033[0m", colornum);
1441 * Detect whether ANSI color is available (answerback)
1443 void send_ansi_detect(void)
1445 if (rc_ansi_color == 2) {
1452 void look_for_ansi(void)
1460 if (rc_ansi_color == 0) {
1462 } else if (rc_ansi_color == 1) {
1464 } else if (rc_ansi_color == 2) {
1466 /* otherwise, do the auto-detect */
1471 if ((now - AnsiDetect) < 2)
1480 select(1, &rfds, NULL, NULL, &tv);
1481 if (FD_ISSET(0, &rfds)) {
1482 abuf[strlen(abuf) + 1] = 0;
1483 read(0, &abuf[strlen(abuf)], 1);
1485 } while (FD_ISSET(0, &rfds));
1487 for (a = 0; a < strlen(abuf); ++a) {
1488 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1489 && (abuf[a + 2] == '?')) {
1498 * Display key options (highlight hotkeys inside angle brackets)
1500 void keyopt(char *buf) {
1504 for (i=0; i<strlen(buf); ++i) {
1507 color(BRIGHT_MAGENTA);
1521 * Present a key-menu line choice type of thing
1523 char keymenu(char *menuprompt, char *menustring) {
1529 int display_prompt = 1;
1531 choices = num_tokens(menustring, '|');
1533 if (menuprompt != NULL) do_prompt = 1;
1534 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1537 if (display_prompt) {
1539 scr_printf("%s ", menuprompt);
1542 for (i=0; i<choices; ++i) {
1543 extract(buf, menustring, i);
1553 if ( (do_prompt) && (ch=='?') ) {
1554 scr_printf("\rOne of... ");
1556 for (i=0; i<choices; ++i) {
1557 extract(buf, menustring, i);
1566 for (i=0; i<choices; ++i) {
1567 extract(buf, menustring, i);
1568 for (c=1; c<strlen(buf); ++c) {
1569 if ( (ch == tolower(buf[c]))
1571 && (buf[c+1]=='>') ) {
1572 for (a=0; a<strlen(buf); ++a) {
1573 if ( (a!=(c-1)) && (a!=(c+1))) {