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
47 #include "citadel_ipc.h"
50 #include "citadel_decls.h"
52 #include "routines2.h"
55 #include "client_chat.h"
68 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
72 char rc_exp_cmd[1024];
73 int rc_allow_attachments;
74 int rc_display_message_numbers;
75 int rc_force_mail_prompts;
76 int rc_remember_passwords;
80 int rc_prompt_control = 0;
81 time_t rc_idle_threshold = (time_t)900;
82 char urls[MAXURLS][SIZ];
84 char rc_gotmail_cmd[SIZ];
87 int next_lazy_cmd = 5;
89 int lines_printed = 0; /* line count for paginator */
90 extern int screenwidth, screenheight;
92 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
94 struct citcmd *cmdlist = NULL;
97 /* these variables are local to this module */
98 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
99 int ok_to_interrupt = 0; /* print express msgs asynchronously */
100 time_t AnsiDetect; /* when did we send the detect code? */
101 int enable_color = 0; /* nonzero for ANSI color */
107 * If an interesting key has been pressed, return its value, otherwise 0
109 char was_a_key_pressed(void) {
119 retval = select(1, &rfds, NULL, NULL, &tv);
121 /* Careful! Disable keepalives during keyboard polling; we're probably
122 * in the middle of a data transfer from the server, in which case
123 * sending a NOOP would throw the client protocol out of sync.
125 if (FD_ISSET(0, &rfds)) {
126 set_keepalives(KA_NO);
127 the_character = inkey();
128 set_keepalives(KA_YES);
133 return(the_character);
141 * Check to see if we need to pause at the end of a screen.
142 * If we do, we have to switch to half keepalives during the pause because
143 * we are probably in the middle of a server operation and the NOOP command
144 * would confuse everything.
146 int checkpagin(int lp, unsigned int pagin, unsigned int height)
150 if (sigcaught) return(lp);
151 thekey = was_a_key_pressed();
152 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
154 if (thekey == 'n' || thekey == 'N')
156 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
157 if (sigcaught) return(lp);
159 if (!pagin) return(0);
160 if (lp>=(height-1)) {
161 set_keepalives(KA_HALF);
163 set_keepalives(KA_YES);
173 * pprintf() ... paginated version of printf()
175 void pprintf(const char *format, ...) {
177 static char buf[4096]; /* static for performance, change if needed */
180 /* If sigcaught is nonzero, a keypress has interrupted this and we
181 * should just drain output.
183 if (sigcaught) return;
185 /* Otherwise, start spewing... */
186 va_start(arg_ptr, format);
187 vsnprintf(buf, sizeof(buf), format, arg_ptr);
190 for (i=0; i<strlen(buf); ++i) {
194 lines_printed = checkpagin(lines_printed,
195 (userflags & US_PAGINATOR),
204 * print_express() - print express messages if there are any
206 void print_express(void)
215 char *listing = NULL;
216 int r; /* IPC result code */
218 if (express_msgs == 0)
224 if (strlen(rc_exp_cmd) == 0) {
229 while (express_msgs != 0) {
230 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
234 express_msgs = extract_int(buf, 0);
235 timestamp = extract_long(buf, 1);
236 flags = extract_int(buf, 2);
237 extract(sender, buf, 3);
238 extract(node, buf, 4);
239 strcpy(last_paged, sender);
241 stamp = localtime(×tamp);
243 /* If the page is a Logoff Request, honor it. */
249 if (strlen(rc_exp_cmd) > 0) {
250 outpipe = popen(rc_exp_cmd, "w");
251 if (outpipe != NULL) {
252 /* Header derived from flags */
255 "Please log off now, as requested ");
257 fprintf(outpipe, "Broadcast message ");
259 fprintf(outpipe, "Chat request ");
261 fprintf(outpipe, "Message ");
262 /* Timestamp. Can this be improved? */
263 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)
264 fprintf(outpipe, "at 12:%02d%cm",
266 stamp->tm_hour ? 'p' : 'a');
267 else if (stamp->tm_hour > 12) /* pm */
268 fprintf(outpipe, "at %d:%02dpm",
272 fprintf(outpipe, "at %d:%02dam",
273 stamp->tm_hour, stamp->tm_min);
274 fprintf(outpipe, " from %s", sender);
275 if (strncmp(serv_info.serv_nodename, node, 32))
276 fprintf(outpipe, " @%s", node);
277 fprintf(outpipe, ":\n%s\n", listing);
279 if (express_msgs == 0)
284 /* fall back to built-in express message display */
288 /* Header derived from flags */
290 scr_printf("Please log off now, as requested ");
292 scr_printf("Broadcast message ");
294 scr_printf("Chat request ");
296 scr_printf("Message ");
298 /* Timestamp. Can this be improved? */
299 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)/* 12am/12pm */
300 scr_printf("at 12:%02d%cm", stamp->tm_min,
301 stamp->tm_hour ? 'p' : 'a');
302 else if (stamp->tm_hour > 12) /* pm */
303 scr_printf("at %d:%02dpm",
304 stamp->tm_hour - 12, stamp->tm_min);
306 scr_printf("at %d:%02dam", stamp->tm_hour, stamp->tm_min);
309 scr_printf(" from %s", sender);
311 /* Remote node, if any */
312 if (strncmp(serv_info.serv_nodename, node, 32))
313 scr_printf(" @%s", node);
317 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
320 /* when running in curses mode, the scroll bar in most
321 xterm-style programs becomes useless, so it makes sense to
322 pause after a screenful of pages if the user has been idle
323 for a while. However, this is annoying to some of the users
324 who aren't in curses mode and tend to leave their clients
325 idle. keepalives become disabled, resulting in getting booted
326 when coming back to the idle session. but they probably have
327 a working scrollback in their terminal, so disable it in this
330 if (!is_curses_enabled())
333 scr_printf("\n---\n");
340 void set_keepalives(int s)
342 keepalives_enabled = (char) s;
346 * This loop handles the "keepalive" messages sent to the server when idling.
349 static time_t idlet = 0;
350 static void really_do_keepalive(void) {
351 int r; /* IPC response code */
355 /* This may sometimes get called before we are actually connected
356 * to the server. Don't do anything if we aren't connected. -IO
358 if (!ipc_for_signal_handlers)
361 /* If full keepalives are enabled, send a NOOP to the server and
362 * wait for a response.
364 if (keepalives_enabled == KA_YES) {
365 r = CtdlIPCNoop(ipc_for_signal_handlers);
366 if (express_msgs > 0) {
367 if (ok_to_interrupt == 1) {
368 scr_printf("\r%64s\r", "");
370 scr_printf("%s%c ", room_name,
371 room_prompt(room_flags));
377 /* If half keepalives are enabled, send a QNOP to the server (if the
378 * server supports it) and then do nothing.
380 if ( (keepalives_enabled == KA_HALF)
381 && (serv_info.serv_supports_qnop > 0) ) {
382 CtdlIPC_putline(ipc_for_signal_handlers, "QNOP");
386 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
387 encapsulated interface; in theory there should be no need to touch these
388 globals outside of the async_ka_* functions. */
390 #ifdef THREADED_CLIENT
391 static pthread_t ka_thr_handle;
392 static int ka_thr_active = 0;
393 static int async_ka_enabled = 0;
395 static void *ka_thread(void *arg)
397 really_do_keepalive();
398 pthread_detach(ka_thr_handle);
403 /* start up a thread to handle a keepalive in the background */
404 static void async_ka_exec(void)
406 if (!ka_thr_active) {
408 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
409 perror("pthread_create");
414 #endif /* THREADED_CLIENT */
416 /* I changed this from static to not because I need to call it from
417 screen.c, either that or make something in screen.c not static.
418 Fix it how you like. Why all the staticness? stu */
420 void do_keepalive(void)
425 if ((now - idlet) < ((long) S_KEEPALIVE))
428 /* Do a space-backspace to keep telnet sessions from idling out */
429 scr_printf(" %c", 8);
432 #ifdef THREADED_CLIENT
433 if (async_ka_enabled)
437 really_do_keepalive();
441 /* Now the actual async-keepalve API that we expose to higher levels:
442 async_ka_start() and async_ka_end(). These do nothing when we don't have
443 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
445 /* wait for a background keepalive to complete. this must be done before
446 attempting any further server requests! */
447 void async_ka_end(void)
449 #ifdef THREADED_CLIENT
451 pthread_join(ka_thr_handle, NULL);
457 /* tell do_keepalive() that keepalives are asynchronous. */
458 void async_ka_start(void)
460 #ifdef THREADED_CLIENT
467 { /* get a character from the keyboard, with */
468 int a; /* the watchdog timer in effect if necessary */
478 /* This loop waits for keyboard input. If the keepalive
479 * timer expires, it sends a keepalive to the server if
480 * necessary and then waits again.
483 scr_set_windowsize();
485 scr_set_windowsize();
489 tv.tv_sec = S_KEEPALIVE;
492 select(1, &rfds, NULL, NULL, &tv);
493 } while (!FD_ISSET(0, &rfds));
495 /* At this point, there's input, so fetch it.
496 * (There's a hole in the bucket...)
498 a = scr_getc(SCR_NOBLOCK);
505 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
506 && ((a < 32) || (a > 126)))
509 #ifndef DISABLE_CURSES
510 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
522 { /* Returns 1 for yes, 0 for no */
538 /* Returns 1 for yes, 0 for no, arg is default value */
561 /* Gets a line from the terminal */
562 /* string == Pointer to string buffer */
563 /* lim == Maximum length - if negative, no-show */
564 void getline(char *string, int lim)
578 if ((a == 8 || a == 23) && (strlen(string) == 0))
580 if ((a != 10) && (a != 8) && (strlen(string) == lim))
582 if ((a == 8) && (string[0] != 0)) {
583 string[strlen(string) - 1] = 0;
584 scr_putc(8); scr_putc(32); scr_putc(8);
587 if ((a == 23) && (string[0] != 0)) {
589 string[strlen(string) - 1] = 0;
590 scr_putc(8); scr_putc(32); scr_putc(8);
591 } while (strlen(string) && string[strlen(string) - 1] != ' ');
613 * strprompt() - prompt for a string, print the existing value and
614 * allow the user to press return to keep it...
616 void strprompt(char *prompt, char *str, int len)
623 scr_printf("%s ", prompt);
626 color(BRIGHT_MAGENTA);
629 scr_printf("%s", str);
632 for (i=0; i<strlen(str); ++i) {
649 * boolprompt() - prompt for a yes/no, print the existing value and
650 * allow the user to press return to keep it...
652 int boolprompt(char *prompt, int prev_val)
657 scr_printf("%s ", prompt);
660 color(BRIGHT_MAGENTA);
661 scr_printf("%s", (prev_val ? "Yes" : "No"));
665 r = (yesno_d(prev_val));
671 * intprompt() - like strprompt(), except for an integer
672 * (note that it RETURNS the new value!)
674 int intprompt(char *prompt, int ival, int imin, int imax)
682 snprintf(buf, sizeof buf, "%d", i);
683 strprompt(prompt, buf, 15);
685 for (p=0; p<strlen(buf); ++p) {
686 if ( (!isdigit(buf[p]))
687 && ( (buf[p]!='-') || (p!=0) ) )
691 scr_printf("*** Must be no less than %d.\n", imin);
693 scr_printf("*** Must be no more than %d.\n", imax);
694 } while ((i < imin) || (i > imax));
699 * newprompt() - prompt for a string with no existing value
700 * (clears out string buffer first)
702 void newprompt(char *prompt, char *str, int len)
704 color(BRIGHT_MAGENTA);
705 scr_printf("%s", prompt);
713 { /* returns a lower case value */
722 * parse the citadel.rc file
724 void load_command_set(void)
728 char editor_key[100];
730 struct citcmd *lastcmd = NULL;
736 /* first, set up some defaults for non-required variables */
738 for (i = 0; i < MAX_EDITORS; i++)
739 strcpy(editor_paths[i], "");
740 strcpy(printcmd, "");
741 strcpy(rc_username, "");
742 strcpy(rc_password, "");
745 rc_allow_attachments = 0;
746 rc_remember_passwords = 0;
747 strcpy(rc_exp_cmd, "");
748 rc_display_message_numbers = 0;
749 rc_force_mail_prompts = 0;
752 strcpy(rc_url_cmd, "");
753 strcpy(rc_gotmail_cmd, "");
755 rc_encrypt = RC_DEFAULT;
757 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
758 rc_screen = RC_DEFAULT;
760 rc_alt_semantics = 0;
762 /* now try to open the citadel.rc file */
765 if (getenv("HOME") != NULL) {
766 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
767 ccfile = fopen(buf, "r");
769 if (ccfile == NULL) {
770 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
771 ccfile = fopen(buf, "r");
773 if (ccfile == NULL) {
774 ccfile = fopen("/etc/citadel.rc", "r");
776 if (ccfile == NULL) {
777 ccfile = fopen("./citadel.rc", "r");
779 if (ccfile == NULL) {
780 perror("commands: cannot open citadel.rc");
783 while (fgets(buf, sizeof buf, ccfile) != NULL) {
784 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
785 buf[strlen(buf) - 1] = 0;
787 if (!strncasecmp(buf, "encrypt=", 8)) {
788 if (!strcasecmp(&buf[8], "yes")) {
792 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
797 else if (!strcasecmp(&buf[8], "no")) {
800 else if (!strcasecmp(&buf[8], "default")) {
801 rc_encrypt = RC_DEFAULT;
806 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
807 if (!strncasecmp(buf, "fullscreen=", 11)) {
808 if (!strcasecmp(&buf[11], "yes"))
810 else if (!strcasecmp(&buf[11], "no"))
815 if (!strncasecmp(buf, "editor=", 7))
816 strcpy(editor_paths[0], &buf[7]);
818 for (i = 0; i < MAX_EDITORS; i++)
820 sprintf(editor_key, "editor%d=", i);
821 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
822 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
825 if (!strncasecmp(buf, "printcmd=", 9))
826 strcpy(printcmd, &buf[9]);
828 if (!strncasecmp(buf, "expcmd=", 7))
829 strcpy(rc_exp_cmd, &buf[7]);
831 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
832 have_xterm = (char) atoi(&buf[24]);
834 if (!strncasecmp(buf, "use_floors=", 11)) {
835 if (!strcasecmp(&buf[11], "yes"))
836 rc_floor_mode = RC_YES;
837 if (!strcasecmp(&buf[11], "no"))
838 rc_floor_mode = RC_NO;
839 if (!strcasecmp(&buf[11], "default"))
840 rc_floor_mode = RC_DEFAULT;
842 if (!strncasecmp(buf, "beep=", 5)) {
843 rc_exp_beep = atoi(&buf[5]);
845 if (!strncasecmp(buf, "allow_attachments=", 18)) {
846 rc_allow_attachments = atoi(&buf[18]);
848 if (!strncasecmp(buf, "idle_threshold=", 15)) {
849 rc_idle_threshold = atoi(&buf[15]);
851 if (!strncasecmp(buf, "remember_passwords=", 19)) {
852 rc_remember_passwords = atoi(&buf[19]);
854 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
855 rc_display_message_numbers = atoi(&buf[24]);
857 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
858 rc_force_mail_prompts = atoi(&buf[19]);
860 if (!strncasecmp(buf, "ansi_color=", 11)) {
861 if (!strncasecmp(&buf[11], "on", 2))
863 if (!strncasecmp(&buf[11], "auto", 4))
864 rc_ansi_color = 2; /* autodetect */
865 if (!strncasecmp(&buf[11], "user", 4))
866 rc_ansi_color = 3; /* user config */
868 if (!strncasecmp(buf, "use_background=", 15)) {
869 if (!strncasecmp(&buf[15], "on", 2))
872 if (!strncasecmp(buf, "prompt_control=", 15)) {
873 if (!strncasecmp(&buf[15], "on", 2))
874 rc_prompt_control = 1;
875 if (!strncasecmp(&buf[15], "user", 4))
876 rc_prompt_control = 3; /* user config */
878 if (!strncasecmp(buf, "username=", 9))
879 strcpy(rc_username, &buf[9]);
881 if (!strncasecmp(buf, "password=", 9))
882 strcpy(rc_password, &buf[9]);
884 if (!strncasecmp(buf, "urlcmd=", 7))
885 strcpy(rc_url_cmd, &buf[7]);
887 if (!strncasecmp(buf, "gotmailcmd=", 11))
888 strcpy(rc_gotmail_cmd, &buf[11]);
890 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
891 if (!strncasecmp(&buf[20], "yes", 3)) {
892 rc_alt_semantics = 1;
895 rc_alt_semantics = 0;
898 if (!strncasecmp(buf, "cmd=", 4)) {
899 strcpy(buf, &buf[4]);
901 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
903 cptr->c_cmdnum = atoi(buf);
904 for (d = strlen(buf); d >= 0; --d)
907 strcpy(buf, &buf[b + 1]);
909 cptr->c_axlevel = atoi(buf);
910 for (d = strlen(buf); d >= 0; --d)
913 strcpy(buf, &buf[b + 1]);
915 for (a = 0; a < 5; ++a)
916 cptr->c_keys[a][0] = 0;
920 buf[strlen(buf) + 1] = 0;
921 while (strlen(buf) > 0) {
923 for (d = strlen(buf); d >= 0; --d)
926 strncpy(cptr->c_keys[a], buf, b);
927 cptr->c_keys[a][b] = 0;
929 strcpy(buf, &buf[b + 1]);
939 lastcmd->next = cptr;
949 * return the key associated with a command
951 char keycmd(char *cmdstr)
955 for (a = 0; a < strlen(cmdstr); ++a)
956 if (cmdstr[a] == '&')
957 return (tolower(cmdstr[a + 1]));
963 * Output the string from a key command without the ampersand
964 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
966 char *cmd_expand(char *strbuf, int mode)
974 for (a = 0; a < strlen(exp); ++a) {
975 if (strbuf[a] == '&') {
978 strcpy(&exp[a], &exp[a + 1]);
982 strcpy(buf, &exp[a + 2]);
988 if (!strncmp(&exp[a], "^r", 2)) {
990 strcpy(&exp[a], room_name);
991 strcat(exp, &buf[a + 2]);
993 if (!strncmp(&exp[a], "^c", 2)) {
995 strcpy(&exp[a + 1], &exp[a + 2]);
1005 * Comparison function to determine if entered commands match a
1006 * command loaded from the config file.
1008 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1019 for (a = 0; a < ncomp; ++a) {
1020 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1021 || (cptr->c_axlevel > cmdax))
1029 * This function returns 1 if a given command requires a string input
1031 int requires_string(struct citcmd *cptr, int ncomp)
1036 strcpy(buf, cptr->c_keys[ncomp - 1]);
1037 for (a = 0; a < strlen(buf); ++a) {
1046 * Input a command at the main prompt.
1047 * This function returns an integer command number. If the command prompts
1048 * for a string then it is placed in the supplied buffer.
1050 int getcmd(CtdlIPC *ipc, char *argbuf)
1059 struct citcmd *cptr;
1062 * Starting a new command now, so set sigcaught to 0. This variable
1063 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1064 * been interrupted by a keypress.
1068 /* Switch color support on or off if we're in user mode */
1069 if (rc_ansi_color == 3) {
1070 if (userflags & US_COLOR)
1075 /* if we're running in idiot mode, display a cute little menu */
1076 IFNEXPERT formout(ipc, "mainmenu");
1081 for (a = 0; a < 5; ++a)
1083 /* now the room prompt... */
1084 ok_to_interrupt = 1;
1085 color(BRIGHT_WHITE);
1086 scr_printf("\n%s", room_name);
1088 scr_printf("%c ", room_prompt(room_flags));
1093 ok_to_interrupt = 0;
1095 /* Handle the backspace key, but only if there's something
1096 * to backspace over...
1098 if ((ch == 8) && (cmdpos > 0)) {
1099 back(cmdspaces[cmdpos - 1] + 1);
1103 /* Spacebar invokes "lazy traversal" commands */
1104 if ((ch == 32) && (cmdpos == 0)) {
1105 this_lazy_cmd = next_lazy_cmd;
1106 if (this_lazy_cmd == 13)
1108 if (this_lazy_cmd == 5)
1110 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1111 if (cptr->c_cmdnum == this_lazy_cmd) {
1112 for (a = 0; a < 5; ++a)
1113 if (cptr->c_keys[a][0] != 0)
1114 scr_printf("%s ", cmd_expand(
1115 cptr->c_keys[a], 0));
1117 return (this_lazy_cmd);
1121 return (this_lazy_cmd);
1123 /* Otherwise, process the command */
1124 cmdbuf[cmdpos] = tolower(ch);
1126 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1127 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1129 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1130 cmdspaces[cmdpos] = strlen(
1131 cmd_expand(cptr->c_keys[cmdpos], 0));
1133 if ((cptr->c_keys[cmdpos + 1]) != 0)
1139 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1140 if (cmdmatch(cmdbuf, cptr, 5)) {
1141 /* We've found our command. */
1142 if (requires_string(cptr, cmdpos)) {
1143 getline(argbuf, 32);
1148 /* If this command is one that changes rooms,
1149 * then the next lazy-command (space bar)
1150 * should be "read new" instead of "goto"
1152 if ((cptr->c_cmdnum == 5)
1153 || (cptr->c_cmdnum == 6)
1154 || (cptr->c_cmdnum == 47)
1155 || (cptr->c_cmdnum == 52)
1156 || (cptr->c_cmdnum == 16)
1157 || (cptr->c_cmdnum == 20))
1160 /* If this command is "read new"
1161 * then the next lazy-command (space bar)
1164 if (cptr->c_cmdnum == 13)
1167 return (cptr->c_cmdnum);
1173 pprintf("\rOne of ... \n");
1174 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1175 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1176 for (a = 0; a < 5; ++a) {
1177 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1183 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1185 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1186 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1187 for (a = 0; a < cmdpos; ++a) {
1189 cmd_expand(cptr->c_keys[a], 0));
1204 * set tty modes. commands are:
1206 * 01- set to bbs mode
1207 * 2 - save current settings for later restoral
1208 * 3 - restore saved settings
1210 #ifdef HAVE_TERMIOS_H
1211 void sttybbs(int cmd)
1212 { /* SysV version of sttybbs() */
1213 struct termios live;
1214 static struct termios saved_settings;
1215 static int last_cmd = 0;
1222 if ((cmd == 0) || (cmd == 1)) {
1223 tcgetattr(0, &live);
1224 live.c_iflag = ISTRIP | IXON | IXANY;
1225 live.c_oflag = OPOST | ONLCR;
1226 live.c_lflag = ISIG | NOFLSH;
1228 live.c_cc[VINTR] = 0;
1229 live.c_cc[VQUIT] = 0;
1232 live.c_cc[VMIN] = 0;
1233 live.c_cc[VTIME] = 0;
1236 /* do we even need this stuff anymore? */
1237 /* live.c_line=0; */
1238 live.c_cc[VERASE] = 8;
1239 live.c_cc[VKILL] = 24;
1240 live.c_cc[VEOF] = 1;
1241 live.c_cc[VEOL] = 255;
1242 live.c_cc[VEOL2] = 0;
1243 live.c_cc[VSTART] = 0;
1244 tcsetattr(0, TCSADRAIN, &live);
1247 tcgetattr(0, &saved_settings);
1250 tcsetattr(0, TCSADRAIN, &saved_settings);
1255 void sttybbs(int cmd)
1256 { /* BSD version of sttybbs() */
1258 static struct sgttyb saved_settings;
1259 static int last_cmd = 0;
1266 if ((cmd == 0) || (cmd == 1)) {
1268 live.sg_flags |= CBREAK;
1269 live.sg_flags |= CRMOD;
1270 live.sg_flags |= NL1;
1271 live.sg_flags &= ~ECHO;
1273 live.sg_flags |= NOFLSH;
1277 gtty(0, &saved_settings);
1280 stty(0, &saved_settings);
1287 * display_help() - help file viewer
1289 void display_help(CtdlIPC *ipc, char *name)
1296 * fmout() - Citadel text formatter and paginator
1299 int width, /* screen width to use */
1300 FILE *fpin, /* file to read from, or NULL to format given text */
1301 char *text, /* text to be formatted (when fpin is NULL */
1302 FILE *fpout, /* file to write to, or NULL to write to screen */
1303 char pagin, /* nonzero if we should use the paginator */
1304 int height, /* screen height to use */
1305 int starting_lp,/* starting value for lines_printed, -1 for global */
1306 int subst) /* nonzero if we should use hypertext mode */
1308 char *buffer = NULL; /* The current message */
1309 char *word = NULL; /* What we are about to actually print */
1310 char *e; /* Pointer to position in text */
1311 char old = 0; /* The previous character */
1312 int column = 0; /* Current column */
1313 size_t i; /* Generic counter */
1315 num_urls = 0; /* Start with a clean slate of embedded URL's */
1317 /* Space for a single word, which can be at most screenwidth */
1318 word = (char *)calloc(1, width);
1320 err_printf("Can't alloc memory to print message: %s!\n",
1325 /* Read the entire message body into memory */
1327 buffer = load_message_from_file(fpin);
1329 err_printf("Can't print message: %s!\n",
1338 if (starting_lp >= 0)
1339 lines_printed = starting_lp;
1341 /* Run the message body */
1343 /* Catch characters that shouldn't be there at all */
1348 /* First, are we looking at a newline? */
1351 if (*e == ' ') { /* Paragraph */
1353 fprintf(fpout, "\n");
1357 lines_printed = checkpagin(lines_printed, pagin, height);
1360 } else if (old != ' ') {/* Don't print two spaces */
1362 fprintf(fpout, " ");
1371 /* Or are we looking at a space? */
1374 if (column >= width - 1) {
1375 /* Are we in the rightmost column? */
1377 fprintf(fpout, "\n");
1381 lines_printed = checkpagin(lines_printed, pagin, height);
1384 } else if (!(column == 0 && old == ' ')) {
1385 /* Eat only the first space on a line */
1387 fprintf(fpout, " ");
1393 /* ONLY eat the FIRST space on a line */
1399 /* Read a word, slightly messy */
1402 if (!isprint(e[i]) && !isspace(e[i]))
1409 /* We should never see these, but... slightly messy */
1410 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1414 * Check for and copy URLs
1415 * We will get the entire URL even if it's longer than the
1416 * screen width, as long as the server didn't break it up
1418 if (!strncasecmp(e, "http://", 7) ||
1419 !strncasecmp(e, "ftp://", 6)) {
1422 strncpy(urls[num_urls], e, i);
1423 urls[num_urls][i] = 0;
1424 for (j = 0; j < strlen(e); j++) {
1427 c = urls[num_urls][j];
1428 if (c == '>' || c == '\"' || c == ')' ||
1429 c == ' ' || c == '\n') {
1430 urls[num_urls][j] = 0;
1437 /* Break up really long words */
1438 /* TODO: auto-hyphenation someday? */
1441 strncpy(word, e, i);
1444 /* Decide where to print the word */
1445 if (column + i >= width) {
1446 /* Wrap to the next line */
1448 fprintf(fpout, "\n");
1452 lines_printed = checkpagin(lines_printed, pagin, height);
1457 /* Print the word */
1459 fprintf(fpout, "%s", word);
1461 scr_printf("%s", word);
1464 e += i; /* Start over with the whitepsace! */
1468 if (fpin) /* We allocated this, remember? */
1471 /* Is this necessary? It makes the output kind of spacey. */
1473 fprintf(fpout, "\n");
1477 lines_printed = checkpagin(lines_printed, pagin, height);
1485 * support ANSI color if defined
1487 void color(int colornum)
1489 static int hold_color;
1490 static int current_color;
1492 if (colornum == COLOR_PUSH) {
1493 hold_color = current_color;
1497 if (colornum == COLOR_POP) {
1502 current_color = colornum;
1504 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1505 if (scr_color(colornum))
1508 /* When switching to dim white, actually output an 'original
1509 * pair' sequence -- this looks better on black-on-white
1510 * terminals. - Changed to ORIGINAL_PAIR as this actually
1511 * wound up looking horrible on black-on-white terminals, not
1512 * to mention transparent terminals.
1514 if (colornum == ORIGINAL_PAIR)
1515 printf("\033[0;39;49m");
1517 printf("\033[%d;3%d;4%dm",
1518 (colornum & 8) ? 1 : 0,
1526 void cls(int colornum)
1529 printf("\033[4%dm\033[2J\033[H\033[0m",
1530 colornum ? colornum : rc_color_use_bg);
1537 * Detect whether ANSI color is available (answerback)
1539 void send_ansi_detect(void)
1541 if (rc_ansi_color == 2) {
1548 void look_for_ansi(void)
1556 if (rc_ansi_color == 0) {
1558 } else if (rc_ansi_color == 1) {
1560 } else if (rc_ansi_color == 2) {
1562 /* otherwise, do the auto-detect */
1567 if ((now - AnsiDetect) < 2)
1576 select(1, &rfds, NULL, NULL, &tv);
1577 if (FD_ISSET(0, &rfds)) {
1578 abuf[strlen(abuf) + 1] = 0;
1579 read(0, &abuf[strlen(abuf)], 1);
1581 } while (FD_ISSET(0, &rfds));
1583 for (a = 0; a < strlen(abuf); ++a) {
1584 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1585 && (abuf[a + 2] == '?')) {
1594 * Display key options (highlight hotkeys inside angle brackets)
1596 void keyopt(char *buf) {
1600 for (i=0; i<strlen(buf); ++i) {
1603 color(BRIGHT_MAGENTA);
1617 * Present a key-menu line choice type of thing
1619 char keymenu(char *menuprompt, char *menustring) {
1625 int display_prompt = 1;
1627 choices = num_tokens(menustring, '|');
1629 if (menuprompt != NULL) do_prompt = 1;
1630 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1633 if (display_prompt) {
1635 scr_printf("%s ", menuprompt);
1638 for (i=0; i<choices; ++i) {
1639 extract(buf, menustring, i);
1649 if ( (do_prompt) && (ch=='?') ) {
1650 scr_printf("\rOne of... ");
1652 for (i=0; i<choices; ++i) {
1653 extract(buf, menustring, i);
1662 for (i=0; i<choices; ++i) {
1663 extract(buf, menustring, i);
1664 for (c=1; c<strlen(buf); ++c) {
1665 if ( (ch == tolower(buf[c]))
1667 && (buf[c+1]=='>') ) {
1668 for (a=0; a<strlen(buf); ++a) {
1669 if ( (a!=(c-1)) && (a!=(c+1))) {