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 time_t rc_idle_threshold = (time_t)900;
80 char urls[MAXURLS][SIZ];
84 int next_lazy_cmd = 5;
86 int lines_printed = 0; /* line count for paginator */
87 extern int screenwidth, screenheight;
90 struct citcmd *cmdlist = NULL;
93 /* these variables are local to this module */
94 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
95 int ok_to_interrupt = 0; /* print express msgs asynchronously */
96 time_t AnsiDetect; /* when did we send the detect code? */
97 int enable_color = 0; /* nonzero for ANSI color */
103 * If an interesting key has been pressed, return its value, otherwise 0
105 char was_a_key_pressed(void) {
115 retval = select(1, &rfds, NULL, NULL, &tv);
117 /* Careful! Disable keepalives during keyboard polling; we're probably
118 * in the middle of a data transfer from the server, in which case
119 * sending a NOOP would throw the client protocol out of sync.
121 if (FD_ISSET(0, &rfds)) {
122 set_keepalives(KA_NO);
123 the_character = inkey();
124 set_keepalives(KA_YES);
129 return(the_character);
137 * Check to see if we need to pause at the end of a screen.
138 * If we do, we have to switch to half keepalives during the pause because
139 * we are probably in the middle of a server operation and the NOOP command
140 * would confuse everything.
142 int checkpagin(int lp, int pagin, int height)
146 if (sigcaught) return(lp);
147 thekey = was_a_key_pressed();
148 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
150 if (thekey == 'n' || thekey == 'N')
152 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
153 if (sigcaught) return(lp);
155 if (!pagin) return(0);
156 if (lp>=(height-1)) {
157 set_keepalives(KA_HALF);
159 set_keepalives(KA_YES);
169 * pprintf() ... paginated version of printf()
171 void pprintf(const char *format, ...) {
173 static char buf[4096]; /* static for performance, change if needed */
176 /* If sigcaught is nonzero, a keypress has interrupted this and we
177 * should just drain output.
179 if (sigcaught) return;
181 /* Otherwise, start spewing... */
182 va_start(arg_ptr, format);
183 vsnprintf(buf, sizeof(buf), format, arg_ptr);
186 for (i=0; i<strlen(buf); ++i) {
190 lines_printed = checkpagin(lines_printed,
191 (userflags & US_PAGINATOR),
200 * print_express() - print express messages if there are any
202 void print_express(void)
212 if (express_msgs == 0)
218 if (strlen(rc_exp_cmd) == 0) {
223 while (express_msgs != 0) {
229 express_msgs = extract_int(&buf[4], 0);
230 timestamp = extract_long(&buf[4], 1);
231 flags = extract_int(&buf[4], 2);
232 extract(sender, &buf[4], 3);
233 extract(node, &buf[4], 4);
234 strcpy(last_paged, sender);
236 stamp = localtime(×tamp);
238 /* If the page is a Logoff Request, honor it. */
244 if (strlen(rc_exp_cmd) > 0) {
245 outpipe = popen(rc_exp_cmd, "w");
246 if (outpipe != NULL) {
247 /* Header derived from flags */
250 "Please log off now, as requested ");
252 fprintf(outpipe, "Broadcast message ");
254 fprintf(outpipe, "Chat request ");
256 fprintf(outpipe, "Message ");
257 /* Timestamp. Can this be improved? */
258 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)
259 fprintf(outpipe, "at 12:%02d%cm",
261 stamp->tm_hour ? 'p' : 'a');
262 else if (stamp->tm_hour > 12) /* pm */
263 fprintf(outpipe, "at %d:%02dpm",
267 fprintf(outpipe, "at %d:%02dam",
268 stamp->tm_hour, stamp->tm_min);
269 fprintf(outpipe, " from %s", sender);
270 if (strncmp(serv_info.serv_nodename, node, 32))
271 fprintf(outpipe, " @%s", node);
272 fprintf(outpipe, ":\n");
273 while (serv_gets(buf), strcmp(buf, "000")) {
274 fprintf(outpipe, "%s\n", buf);
277 if (express_msgs == 0)
282 /* fall back to built-in express message display */
286 /* Header derived from flags */
288 scr_printf("Please log off now, as requested ");
290 scr_printf("Broadcast message ");
292 scr_printf("Chat request ");
294 scr_printf("Message ");
296 /* Timestamp. Can this be improved? */
297 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)/* 12am/12pm */
298 scr_printf("at 12:%02d%cm", stamp->tm_min,
299 stamp->tm_hour ? 'p' : 'a');
300 else if (stamp->tm_hour > 12) /* pm */
301 scr_printf("at %d:%02dpm",
302 stamp->tm_hour - 12, stamp->tm_min);
304 scr_printf("at %d:%02dam", stamp->tm_hour, stamp->tm_min);
307 scr_printf(" from %s", sender);
309 /* Remote node, if any */
310 if (strncmp(serv_info.serv_nodename, node, 32))
311 scr_printf(" @%s", node);
315 fmout(screenwidth, NULL, NULL, 1, screenheight, -1, 0);
317 /* when running in curses mode, the scroll bar in most
318 xterm-style programs becomes useless, so it makes sense to
319 pause after a screenful of pages if the user has been idle
320 for a while. However, this is annoying to some of the users
321 who aren't in curses mode and tend to leave their clients
322 idle. keepalives become disabled, resulting in getting booted
323 when coming back to the idle session. but they probably have
324 a working scrollback in their terminal, so disable it in this
327 if (!is_curses_enabled())
330 scr_printf("\n---\n");
337 void set_keepalives(int s)
339 keepalives_enabled = (char) s;
343 * This loop handles the "keepalive" messages sent to the server when idling.
346 static time_t idlet = 0;
347 static void really_do_keepalive(void) {
352 /* If full keepalives are enabled, send a NOOP to the server and
353 * wait for a response.
355 if (keepalives_enabled == KA_YES) {
360 if (ok_to_interrupt == 1) {
361 scr_printf("\r%64s\r", "");
363 scr_printf("%s%c ", room_name,
364 room_prompt(room_flags));
370 /* If half keepalives are enabled, send a QNOP to the server (if the
371 * server supports it) and then do nothing.
373 if ( (keepalives_enabled == KA_HALF)
374 && (serv_info.serv_supports_qnop > 0) ) {
379 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
380 encapsulated interface; in theory there should be no need to touch these
381 globals outside of the async_ka_* functions. */
383 #ifdef THREADED_CLIENT
384 static pthread_t ka_thr_handle;
385 static int ka_thr_active = 0;
386 static int async_ka_enabled = 0;
388 static void *ka_thread(void *arg)
390 really_do_keepalive();
391 pthread_detach(ka_thr_handle);
396 /* start up a thread to handle a keepalive in the background */
397 static void async_ka_exec(void)
399 if (!ka_thr_active) {
401 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
402 perror("pthread_create");
407 #endif /* THREADED_CLIENT */
409 /* I changed this from static to not because I need to call it from
410 screen.c, either that or make something in screen.c not static.
411 Fix it how you like. Why all the staticness? stu */
413 void do_keepalive(void)
418 if ((now - idlet) < ((long) S_KEEPALIVE))
421 /* Do a space-backspace to keep telnet sessions from idling out */
422 scr_printf(" %c", 8);
425 #ifdef THREADED_CLIENT
426 if (async_ka_enabled)
430 really_do_keepalive();
434 /* Now the actual async-keepalve API that we expose to higher levels:
435 async_ka_start() and async_ka_end(). These do nothing when we don't have
436 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
438 /* wait for a background keepalive to complete. this must be done before
439 attempting any further server requests! */
440 void async_ka_end(void)
442 #ifdef THREADED_CLIENT
444 pthread_join(ka_thr_handle, NULL);
450 /* tell do_keepalive() that keepalives are asynchronous. */
451 void async_ka_start(void)
453 #ifdef THREADED_CLIENT
460 { /* get a character from the keyboard, with */
461 int a; /* the watchdog timer in effect if necessary */
471 /* This loop waits for keyboard input. If the keepalive
472 * timer expires, it sends a keepalive to the server if
473 * necessary and then waits again.
476 scr_set_windowsize();
478 scr_set_windowsize();
482 tv.tv_sec = S_KEEPALIVE;
485 select(1, &rfds, NULL, NULL, &tv);
486 } while (!FD_ISSET(0, &rfds));
488 /* At this point, there's input, so fetch it.
489 * (There's a hole in the bucket...)
491 a = scr_getc(SCR_NOBLOCK);
498 if (((a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
499 && ((a < 32) || (a > 126)))
502 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
513 { /* Returns 1 for yes, 0 for no */
529 /* Returns 1 for yes, 0 for no, arg is default value */
552 /* Gets a line from the terminal */
553 /* string == Pointer to string buffer */
554 /* lim == Maximum length - if negative, no-show */
555 void getline(char *string, int lim)
569 if ((a == 8) && (strlen(string) == 0))
571 if ((a != 10) && (a != 8) && (strlen(string) == lim))
573 if ((a == 8) && (string[0] != 0)) {
574 string[strlen(string) - 1] = 0;
599 * strprompt() - prompt for a string, print the existing value and
600 * allow the user to press return to keep it...
602 void strprompt(char *prompt, char *str, int len)
609 scr_printf("%s ", prompt);
612 color(BRIGHT_MAGENTA);
615 scr_printf("%s", str);
618 for (i=0; i<strlen(str); ++i) {
635 * boolprompt() - prompt for a yes/no, print the existing value and
636 * allow the user to press return to keep it...
638 int boolprompt(char *prompt, int prev_val)
643 scr_printf("%s ", prompt);
646 color(BRIGHT_MAGENTA);
647 scr_printf("%s", (prev_val ? "Yes" : "No"));
651 r = (yesno_d(prev_val));
657 * intprompt() - like strprompt(), except for an integer
658 * (note that it RETURNS the new value!)
660 int intprompt(char *prompt, int ival, int imin, int imax)
668 snprintf(buf, sizeof buf, "%d", i);
669 strprompt(prompt, buf, 15);
671 for (p=0; p<strlen(buf); ++p) {
672 if ( (!isdigit(buf[p]))
673 && ( (buf[p]!='-') || (p!=0) ) )
677 scr_printf("*** Must be no less than %d.\n", imin);
679 scr_printf("*** Must be no more than %d.\n", imax);
680 } while ((i < imin) || (i > imax));
685 * newprompt() - prompt for a string with no existing value
686 * (clears out string buffer first)
688 void newprompt(char *prompt, char *str, int len)
690 color(BRIGHT_MAGENTA);
691 scr_printf("%s", prompt);
699 { /* returns a lower case value */
708 * parse the citadel.rc file
710 void load_command_set(void)
715 struct citcmd *lastcmd = NULL;
720 /* first, set up some defaults for non-required variables */
722 strcpy(editor_path, "");
723 strcpy(printcmd, "");
724 strcpy(rc_username, "");
725 strcpy(rc_password, "");
728 rc_allow_attachments = 0;
729 rc_remember_passwords = 0;
730 strcpy(rc_exp_cmd, "");
731 rc_display_message_numbers = 0;
732 rc_force_mail_prompts = 0;
734 strcpy(rc_url_cmd, "");
736 rc_encrypt = RC_DEFAULT;
739 rc_screen = RC_DEFAULT;
741 rc_alt_semantics = 0;
743 /* now try to open the citadel.rc file */
746 if (getenv("HOME") != NULL) {
747 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
748 ccfile = fopen(buf, "r");
750 if (ccfile == NULL) {
751 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
752 ccfile = fopen(buf, "r");
754 if (ccfile == NULL) {
755 ccfile = fopen("/etc/citadel.rc", "r");
757 if (ccfile == NULL) {
758 ccfile = fopen("./citadel.rc", "r");
760 if (ccfile == NULL) {
761 perror("commands: cannot open citadel.rc");
764 while (fgets(buf, sizeof buf, ccfile) != NULL) {
765 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
766 buf[strlen(buf) - 1] = 0;
768 if (!strncasecmp(buf, "encrypt=", 8)) {
769 if (!strcasecmp(&buf[8], "yes")) {
773 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
778 else if (!strcasecmp(&buf[8], "no")) {
781 else if (!strcasecmp(&buf[8], "default")) {
782 rc_encrypt = RC_DEFAULT;
788 if (!strncasecmp(buf, "fullscreen=", 11)) {
789 if (!strcasecmp(&buf[11], "yes"))
791 else if (!strcasecmp(&buf[11], "no"))
796 if (!strncasecmp(buf, "editor=", 7))
797 strcpy(editor_path, &buf[7]);
799 if (!strncasecmp(buf, "printcmd=", 9))
800 strcpy(printcmd, &buf[9]);
802 if (!strncasecmp(buf, "expcmd=", 7))
803 strcpy(rc_exp_cmd, &buf[7]);
805 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
806 have_xterm = (char) atoi(&buf[24]);
808 if (!strncasecmp(buf, "use_floors=", 11)) {
809 if (!strcasecmp(&buf[11], "yes"))
810 rc_floor_mode = RC_YES;
811 if (!strcasecmp(&buf[11], "no"))
812 rc_floor_mode = RC_NO;
813 if (!strcasecmp(&buf[11], "default"))
814 rc_floor_mode = RC_DEFAULT;
816 if (!strncasecmp(buf, "beep=", 5)) {
817 rc_exp_beep = atoi(&buf[5]);
819 if (!strncasecmp(buf, "allow_attachments=", 18)) {
820 rc_allow_attachments = atoi(&buf[18]);
822 if (!strncasecmp(buf, "idle_threshold=", 15)) {
823 rc_idle_threshold = atoi(&buf[15]);
825 if (!strncasecmp(buf, "remember_passwords=", 19)) {
826 rc_remember_passwords = atoi(&buf[19]);
828 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
829 rc_display_message_numbers = atoi(&buf[24]);
831 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
832 rc_force_mail_prompts = atoi(&buf[19]);
834 if (!strncasecmp(buf, "ansi_color=", 11)) {
835 if (!strncasecmp(&buf[11], "on", 2))
837 if (!strncasecmp(&buf[11], "auto", 4))
838 rc_ansi_color = 2; /* autodetect */
839 if (!strncasecmp(&buf[11], "user", 4))
840 rc_ansi_color = 3; /* user config */
842 if (!strncasecmp(buf, "prompt_control=", 15)) {
843 if (!strncasecmp(&buf[15], "on", 2))
844 rc_prompt_control = 1;
845 if (!strncasecmp(&buf[15], "user", 4))
846 rc_prompt_control = 3; /* user config */
848 if (!strncasecmp(buf, "username=", 9))
849 strcpy(rc_username, &buf[9]);
851 if (!strncasecmp(buf, "password=", 9))
852 strcpy(rc_password, &buf[9]);
854 if (!strncasecmp(buf, "urlcmd=", 7))
855 strcpy(rc_url_cmd, &buf[7]);
857 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
858 if (!strncasecmp(&buf[11], "yes", 3))
859 rc_alt_semantics = 1;
860 if (!strncasecmp(&buf[11], "no", 2))
861 rc_alt_semantics = 0;
863 if (!strncasecmp(buf, "cmd=", 4)) {
864 strcpy(buf, &buf[4]);
866 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
868 cptr->c_cmdnum = atoi(buf);
869 for (d = strlen(buf); d >= 0; --d)
872 strcpy(buf, &buf[b + 1]);
874 cptr->c_axlevel = atoi(buf);
875 for (d = strlen(buf); d >= 0; --d)
878 strcpy(buf, &buf[b + 1]);
880 for (a = 0; a < 5; ++a)
881 cptr->c_keys[a][0] = 0;
885 buf[strlen(buf) + 1] = 0;
886 while (strlen(buf) > 0) {
888 for (d = strlen(buf); d >= 0; --d)
891 strncpy(cptr->c_keys[a], buf, b);
892 cptr->c_keys[a][b] = 0;
894 strcpy(buf, &buf[b + 1]);
904 lastcmd->next = cptr;
914 * return the key associated with a command
916 char keycmd(char *cmdstr)
920 for (a = 0; a < strlen(cmdstr); ++a)
921 if (cmdstr[a] == '&')
922 return (tolower(cmdstr[a + 1]));
928 * Output the string from a key command without the ampersand
929 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
931 char *cmd_expand(char *strbuf, int mode)
939 for (a = 0; a < strlen(exp); ++a) {
940 if (strbuf[a] == '&') {
943 strcpy(&exp[a], &exp[a + 1]);
947 strcpy(buf, &exp[a + 2]);
953 if (!strncmp(&exp[a], "^r", 2)) {
955 strcpy(&exp[a], room_name);
956 strcat(exp, &buf[a + 2]);
958 if (!strncmp(&exp[a], "^c", 2)) {
960 strcpy(&exp[a + 1], &exp[a + 2]);
970 * Comparison function to determine if entered commands match a
971 * command loaded from the config file.
973 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
984 for (a = 0; a < ncomp; ++a) {
985 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
986 || (cptr->c_axlevel > cmdax))
994 * This function returns 1 if a given command requires a string input
996 int requires_string(struct citcmd *cptr, int ncomp)
1001 strcpy(buf, cptr->c_keys[ncomp - 1]);
1002 for (a = 0; a < strlen(buf); ++a) {
1011 * Input a command at the main prompt.
1012 * This function returns an integer command number. If the command prompts
1013 * for a string then it is placed in the supplied buffer.
1015 int getcmd(char *argbuf)
1024 struct citcmd *cptr;
1027 * Starting a new command now, so set sigcaught to 0. This variable
1028 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1029 * been interrupted by a keypress.
1033 /* Switch color support on or off if we're in user mode */
1034 if (rc_ansi_color == 3) {
1035 if (userflags & US_COLOR)
1040 /* if we're running in idiot mode, display a cute little menu */
1041 IFNEXPERT formout("mainmenu");
1043 print_express(); /* print express messages if there are any */
1046 for (a = 0; a < 5; ++a)
1048 /* now the room prompt... */
1049 ok_to_interrupt = 1;
1050 color(BRIGHT_WHITE);
1051 scr_printf("\n%s", room_name);
1053 scr_printf("%c ", room_prompt(room_flags));
1058 ok_to_interrupt = 0;
1060 /* Handle the backspace key, but only if there's something
1061 * to backspace over...
1063 if ((ch == 8) && (cmdpos > 0)) {
1064 back(cmdspaces[cmdpos - 1] + 1);
1068 /* Spacebar invokes "lazy traversal" commands */
1069 if ((ch == 32) && (cmdpos == 0)) {
1070 this_lazy_cmd = next_lazy_cmd;
1071 if (this_lazy_cmd == 13)
1073 if (this_lazy_cmd == 5)
1075 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1076 if (cptr->c_cmdnum == this_lazy_cmd) {
1077 for (a = 0; a < 5; ++a)
1078 if (cptr->c_keys[a][0] != 0)
1079 scr_printf("%s ", cmd_expand(
1080 cptr->c_keys[a], 0));
1082 return (this_lazy_cmd);
1086 return (this_lazy_cmd);
1088 /* Otherwise, process the command */
1089 cmdbuf[cmdpos] = tolower(ch);
1091 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1092 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1094 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1095 cmdspaces[cmdpos] = strlen(
1096 cmd_expand(cptr->c_keys[cmdpos], 0));
1098 if ((cptr->c_keys[cmdpos + 1]) != 0)
1104 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1105 if (cmdmatch(cmdbuf, cptr, 5)) {
1106 /* We've found our command. */
1107 if (requires_string(cptr, cmdpos)) {
1108 getline(argbuf, 32);
1113 /* If this command is one that changes rooms,
1114 * then the next lazy-command (space bar)
1115 * should be "read new" instead of "goto"
1117 if ((cptr->c_cmdnum == 5)
1118 || (cptr->c_cmdnum == 6)
1119 || (cptr->c_cmdnum == 47)
1120 || (cptr->c_cmdnum == 52)
1121 || (cptr->c_cmdnum == 16)
1122 || (cptr->c_cmdnum == 20))
1125 return (cptr->c_cmdnum);
1131 pprintf("\rOne of ... \n");
1132 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1133 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1134 for (a = 0; a < 5; ++a) {
1135 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1141 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1143 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1144 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1145 for (a = 0; a < cmdpos; ++a) {
1147 cmd_expand(cptr->c_keys[a], 0));
1162 * set tty modes. commands are:
1164 * 01- set to bbs mode
1165 * 2 - save current settings for later restoral
1166 * 3 - restore saved settings
1168 #ifdef HAVE_TERMIOS_H
1169 void sttybbs(int cmd)
1170 { /* SysV version of sttybbs() */
1171 struct termios live;
1172 static struct termios saved_settings;
1173 static int last_cmd = 0;
1180 if ((cmd == 0) || (cmd == 1)) {
1181 tcgetattr(0, &live);
1182 live.c_iflag = ISTRIP | IXON | IXANY;
1183 live.c_oflag = OPOST | ONLCR;
1184 live.c_lflag = ISIG | NOFLSH;
1186 live.c_cc[VINTR] = (-1);
1187 live.c_cc[VQUIT] = (-1);
1190 live.c_cc[VMIN] = 0;
1191 live.c_cc[VTIME] = 0;
1194 /* do we even need this stuff anymore? */
1195 /* live.c_line=0; */
1196 live.c_cc[VERASE] = 8;
1197 live.c_cc[VKILL] = 24;
1198 live.c_cc[VEOF] = 1;
1199 live.c_cc[VEOL] = 255;
1200 live.c_cc[VEOL2] = 0;
1201 live.c_cc[VSTART] = 0;
1202 tcsetattr(0, TCSADRAIN, &live);
1205 tcgetattr(0, &saved_settings);
1208 tcsetattr(0, TCSADRAIN, &saved_settings);
1212 void sttybbs(int cmd)
1213 { /* BSD version of sttybbs() */
1215 static struct sgttyb saved_settings;
1216 static int last_cmd = 0;
1223 if ((cmd == 0) || (cmd == 1)) {
1225 live.sg_flags |= CBREAK;
1226 live.sg_flags |= CRMOD;
1227 live.sg_flags |= NL1;
1228 live.sg_flags &= ~ECHO;
1230 live.sg_flags |= NOFLSH;
1234 gtty(0, &saved_settings);
1237 stty(0, &saved_settings);
1244 * display_help() - help file viewer
1246 void display_help(char *name)
1253 * fmout() - Citadel text formatter and paginator
1256 int width, /* screen width to use */
1257 FILE *fpin, /* file to read from, or NULL to read from server */
1258 FILE *fpout, /* File to write to, or NULL to write to screen */
1259 char pagin, /* nonzero if we should use the paginator */
1260 int height, /* screen height to use */
1261 int starting_lp,/* starting value for lines_printed, -1 for global */
1262 char subst) /* nonzero if we should use hypertext mode */
1264 int a, b, c, d, old;
1270 num_urls = 0; /* Start with a clean slate of embedded URL's */
1272 if (starting_lp >= 0) {
1273 lines_printed = starting_lp;
1278 c = 1; /* c is the current pos */
1280 FMTA: while ((eof_flag == 0) && (strlen(buffer) < 126)) {
1281 if (fpin != NULL) { /* read from file */
1284 if (eof_flag == 0) {
1286 buffer[strlen(buffer) + 1] = 0;
1287 buffer[strlen(buffer)] = a;
1289 } else { /* read from server */
1291 serv_gets(&buffer[d]);
1292 while ((!isspace(buffer[d])) && (isspace(buffer[strlen(buffer) - 1])))
1293 buffer[strlen(buffer) - 1] = 0;
1294 if (!strcmp(&buffer[d], "000")) {
1297 while (isspace(buffer[strlen(buffer) - 1]))
1298 buffer[strlen(buffer) - 1] = 0;
1306 if ( (!strncasecmp(buffer, "http://", 7))
1307 || (!strncasecmp(buffer, "ftp://", 6)) ) {
1308 safestrncpy(urls[num_urls], buffer, (SIZ-1));
1309 for (a=0; a<strlen(urls[num_urls]); ++a) {
1310 b = urls[num_urls][a];
1311 if ( (b==' ') || (b==')') || (b=='>') || (b==10)
1312 || (b==13) || (b==9) || (b=='\"') )
1313 urls[num_urls][a] = 0;
1318 buffer[strlen(buffer) + 1] = 0;
1320 strcpy(buffer, &buffer[1]);
1327 if (((a == 13) || (a == 10)) && (old != 13) && (old != 10))
1329 if (((old == 13) || (old == 10)) && (isspace(real))) {
1331 fprintf(fpout, "\n");
1335 lines_printed = checkpagin(lines_printed, pagin, height);
1343 if (((strlen(aaa) + c) > (width - 1)) && (strlen(aaa) > (width - 1))) {
1345 fprintf(fpout, "\n%s", aaa);
1347 scr_printf("\n%s", aaa);
1349 lines_printed = checkpagin(lines_printed, pagin, height);
1359 if ((strlen(aaa) + c) > (width - 1)) {
1362 fprintf(fpout, "\n");
1366 lines_printed = checkpagin(lines_printed, pagin, height);
1370 fprintf(fpout, "%s ", aaa);
1372 scr_printf("%s ", aaa);
1375 c = c + strlen(aaa);
1379 if ((a == 13) || (a == 10)) {
1381 fprintf(fpout, "%s\n", aaa);
1383 scr_printf("%s\n", aaa);
1385 lines_printed = checkpagin(lines_printed, pagin, height);
1388 if (sigcaught) goto OOPS;
1394 /* keypress caught; drain the server */
1397 } while (strcmp(aaa, "000"));
1401 fprintf(fpout, "\n");
1405 lines_printed = checkpagin(lines_printed, pagin, height);
1412 * support ANSI color if defined
1414 void color(int colornum)
1416 static int is_bold = 0;
1417 static int hold_color, current_color;
1419 if (colornum == COLOR_PUSH) {
1420 hold_color = current_color;
1424 if (colornum == COLOR_POP) {
1429 current_color = colornum;
1431 #ifdef HAVE_CURSES_H
1432 if (scr_color(colornum))
1435 /* When switching to dim white, actually output an 'original
1436 * pair' sequence -- this looks better on black-on-white
1439 if (colornum == DIM_WHITE)
1440 printf("\033[39;49m");
1442 printf("\033[3%d;40m", (colornum & 7));
1444 if ((colornum >= 8) && (is_bold == 0)) {
1447 } else if ((colornum < 8) && (is_bold == 1)) {
1455 void cls(int colornum)
1458 printf("\033[4%dm\033[2J\033[H\033[0m", colornum);
1465 * Detect whether ANSI color is available (answerback)
1467 void send_ansi_detect(void)
1469 if (rc_ansi_color == 2) {
1476 void look_for_ansi(void)
1484 if (rc_ansi_color == 0) {
1486 } else if (rc_ansi_color == 1) {
1488 } else if (rc_ansi_color == 2) {
1490 /* otherwise, do the auto-detect */
1495 if ((now - AnsiDetect) < 2)
1504 select(1, &rfds, NULL, NULL, &tv);
1505 if (FD_ISSET(0, &rfds)) {
1506 abuf[strlen(abuf) + 1] = 0;
1507 read(0, &abuf[strlen(abuf)], 1);
1509 } while (FD_ISSET(0, &rfds));
1511 for (a = 0; a < strlen(abuf); ++a) {
1512 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1513 && (abuf[a + 2] == '?')) {
1522 * Display key options (highlight hotkeys inside angle brackets)
1524 void keyopt(char *buf) {
1528 for (i=0; i<strlen(buf); ++i) {
1531 color(BRIGHT_MAGENTA);
1545 * Present a key-menu line choice type of thing
1547 char keymenu(char *menuprompt, char *menustring) {
1553 int display_prompt = 1;
1555 choices = num_tokens(menustring, '|');
1557 if (menuprompt != NULL) do_prompt = 1;
1558 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1561 if (display_prompt) {
1563 scr_printf("%s ", menuprompt);
1566 for (i=0; i<choices; ++i) {
1567 extract(buf, menustring, i);
1577 if ( (do_prompt) && (ch=='?') ) {
1578 scr_printf("\rOne of... ");
1580 for (i=0; i<choices; ++i) {
1581 extract(buf, menustring, i);
1590 for (i=0; i<choices; ++i) {
1591 extract(buf, menustring, i);
1592 for (c=1; c<strlen(buf); ++c) {
1593 if ( (ch == tolower(buf[c]))
1595 && (buf[c+1]=='>') ) {
1596 for (a=0; a<strlen(buf); ++a) {
1597 if ( (a!=(c-1)) && (a!=(c+1))) {