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;
79 int rc_prompt_control = 0;
80 time_t rc_idle_threshold = (time_t)900;
82 char rc_gotmail_cmd[SIZ];
85 int next_lazy_cmd = 5;
87 int lines_printed = 0; /* line count for paginator */
88 extern int screenwidth, screenheight;
90 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
92 struct citcmd *cmdlist = NULL;
95 /* these variables are local to this module */
96 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
97 int ok_to_interrupt = 0; /* print express msgs asynchronously */
98 time_t AnsiDetect; /* when did we send the detect code? */
99 int enable_color = 0; /* nonzero for ANSI color */
105 * If an interesting key has been pressed, return its value, otherwise 0
107 char was_a_key_pressed(void) {
117 retval = select(1, &rfds, NULL, NULL, &tv);
119 /* Careful! Disable keepalives during keyboard polling; we're probably
120 * in the middle of a data transfer from the server, in which case
121 * sending a NOOP would throw the client protocol out of sync.
123 if (FD_ISSET(0, &rfds)) {
124 set_keepalives(KA_NO);
125 the_character = inkey();
126 set_keepalives(KA_YES);
131 return(the_character);
139 * Check to see if we need to pause at the end of a screen.
140 * If we do, we have to switch to half keepalives during the pause because
141 * we are probably in the middle of a server operation and the NOOP command
142 * would confuse everything.
144 int checkpagin(int lp, unsigned int pagin, unsigned int height)
148 if (sigcaught) return(lp);
149 thekey = was_a_key_pressed();
150 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
152 if (thekey == 'n' || thekey == 'N')
154 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
155 if (sigcaught) return(lp);
157 if (!pagin) return(0);
158 if (lp>=(height-1)) {
159 set_keepalives(KA_HALF);
161 set_keepalives(KA_YES);
171 * pprintf() ... paginated version of printf()
173 void pprintf(const char *format, ...) {
175 static char buf[4096]; /* static for performance, change if needed */
178 /* If sigcaught is nonzero, a keypress has interrupted this and we
179 * should just drain output.
181 if (sigcaught) return;
183 /* Otherwise, start spewing... */
184 va_start(arg_ptr, format);
185 vsnprintf(buf, sizeof(buf), format, arg_ptr);
188 for (i=0; i<strlen(buf); ++i) {
192 lines_printed = checkpagin(lines_printed,
193 (userflags & US_PAGINATOR),
202 * print_express() - print express messages if there are any
204 void print_express(void)
213 char *listing = NULL;
214 int r; /* IPC result code */
216 if (express_msgs == 0)
222 if (strlen(rc_exp_cmd) == 0) {
227 while (express_msgs != 0) {
228 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
232 express_msgs = extract_int(buf, 0);
233 timestamp = extract_long(buf, 1);
234 flags = extract_int(buf, 2);
235 extract(sender, buf, 3);
236 extract(node, buf, 4);
237 strcpy(last_paged, sender);
239 stamp = localtime(×tamp);
241 /* If the page is a Logoff Request, honor it. */
247 if (strlen(rc_exp_cmd) > 0) {
248 outpipe = popen(rc_exp_cmd, "w");
249 if (outpipe != NULL) {
250 /* Header derived from flags */
253 "Please log off now, as requested ");
255 fprintf(outpipe, "Broadcast message ");
257 fprintf(outpipe, "Chat request ");
259 fprintf(outpipe, "Message ");
260 /* Timestamp. Can this be improved? */
261 if (stamp->tm_hour == 0 || stamp->tm_hour == 12)
262 fprintf(outpipe, "at 12:%02d%cm",
264 stamp->tm_hour ? 'p' : 'a');
265 else if (stamp->tm_hour > 12) /* pm */
266 fprintf(outpipe, "at %d:%02dpm",
270 fprintf(outpipe, "at %d:%02dam",
271 stamp->tm_hour, stamp->tm_min);
272 fprintf(outpipe, " from %s", sender);
273 if (strncmp(serv_info.serv_nodename, node, 32))
274 fprintf(outpipe, " @%s", node);
275 fprintf(outpipe, ":\n%s\n", listing);
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, listing, NULL, 1, screenheight, -1, 0);
318 /* when running in curses mode, the scroll bar in most
319 xterm-style programs becomes useless, so it makes sense to
320 pause after a screenful of pages if the user has been idle
321 for a while. However, this is annoying to some of the users
322 who aren't in curses mode and tend to leave their clients
323 idle. keepalives become disabled, resulting in getting booted
324 when coming back to the idle session. but they probably have
325 a working scrollback in their terminal, so disable it in this
328 if (!is_curses_enabled())
331 scr_printf("\n---\n");
338 void set_keepalives(int s)
340 keepalives_enabled = (char) s;
344 * This loop handles the "keepalive" messages sent to the server when idling.
347 static time_t idlet = 0;
348 static void really_do_keepalive(void) {
349 int r; /* IPC response code */
353 /* This may sometimes get called before we are actually connected
354 * to the server. Don't do anything if we aren't connected. -IO
356 if (!ipc_for_signal_handlers)
359 /* If full keepalives are enabled, send a NOOP to the server and
360 * wait for a response.
362 if (keepalives_enabled == KA_YES) {
363 r = CtdlIPCNoop(ipc_for_signal_handlers);
364 if (express_msgs > 0) {
365 if (ok_to_interrupt == 1) {
366 scr_printf("\r%64s\r", "");
368 scr_printf("%s%c ", room_name,
369 room_prompt(room_flags));
375 /* If half keepalives are enabled, send a QNOP to the server (if the
376 * server supports it) and then do nothing.
378 if ( (keepalives_enabled == KA_HALF)
379 && (serv_info.serv_supports_qnop > 0) ) {
380 CtdlIPC_putline(ipc_for_signal_handlers, "QNOP");
384 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
385 encapsulated interface; in theory there should be no need to touch these
386 globals outside of the async_ka_* functions. */
388 #ifdef THREADED_CLIENT
389 static pthread_t ka_thr_handle;
390 static int ka_thr_active = 0;
391 static int async_ka_enabled = 0;
393 static void *ka_thread(void *arg)
395 really_do_keepalive();
396 pthread_detach(ka_thr_handle);
401 /* start up a thread to handle a keepalive in the background */
402 static void async_ka_exec(void)
404 if (!ka_thr_active) {
406 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
407 perror("pthread_create");
412 #endif /* THREADED_CLIENT */
414 /* I changed this from static to not because I need to call it from
415 screen.c, either that or make something in screen.c not static.
416 Fix it how you like. Why all the staticness? stu */
418 void do_keepalive(void)
423 if ((now - idlet) < ((long) S_KEEPALIVE))
426 /* Do a space-backspace to keep telnet sessions from idling out */
427 scr_printf(" %c", 8);
430 #ifdef THREADED_CLIENT
431 if (async_ka_enabled)
435 really_do_keepalive();
439 /* Now the actual async-keepalve API that we expose to higher levels:
440 async_ka_start() and async_ka_end(). These do nothing when we don't have
441 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
443 /* wait for a background keepalive to complete. this must be done before
444 attempting any further server requests! */
445 void async_ka_end(void)
447 #ifdef THREADED_CLIENT
449 pthread_join(ka_thr_handle, NULL);
455 /* tell do_keepalive() that keepalives are asynchronous. */
456 void async_ka_start(void)
458 #ifdef THREADED_CLIENT
465 { /* get a character from the keyboard, with */
466 int a; /* the watchdog timer in effect if necessary */
476 /* This loop waits for keyboard input. If the keepalive
477 * timer expires, it sends a keepalive to the server if
478 * necessary and then waits again.
481 scr_set_windowsize();
483 scr_set_windowsize();
487 tv.tv_sec = S_KEEPALIVE;
490 select(1, &rfds, NULL, NULL, &tv);
491 } while (!FD_ISSET(0, &rfds));
493 /* At this point, there's input, so fetch it.
494 * (There's a hole in the bucket...)
496 a = scr_getc(SCR_NOBLOCK);
503 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
504 && ((a < 32) || (a > 126)))
507 #ifndef DISABLE_CURSES
508 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
520 { /* Returns 1 for yes, 0 for no */
536 /* Returns 1 for yes, 0 for no, arg is default value */
559 /* Gets a line from the terminal */
560 /* string == Pointer to string buffer */
561 /* lim == Maximum length - if negative, no-show */
562 void getline(char *string, int lim)
576 if ((a == 8 || a == 23) && (strlen(string) == 0))
578 if ((a != 10) && (a != 8) && (strlen(string) == lim))
580 if ((a == 8) && (string[0] != 0)) {
581 string[strlen(string) - 1] = 0;
582 scr_putc(8); scr_putc(32); scr_putc(8);
585 if ((a == 23) && (string[0] != 0)) {
587 string[strlen(string) - 1] = 0;
588 scr_putc(8); scr_putc(32); scr_putc(8);
589 } while (strlen(string) && string[strlen(string) - 1] != ' ');
611 * strprompt() - prompt for a string, print the existing value and
612 * allow the user to press return to keep it...
614 void strprompt(char *prompt, char *str, int len)
621 scr_printf("%s ", prompt);
624 color(BRIGHT_MAGENTA);
627 scr_printf("%s", str);
630 for (i=0; i<strlen(str); ++i) {
647 * boolprompt() - prompt for a yes/no, print the existing value and
648 * allow the user to press return to keep it...
650 int boolprompt(char *prompt, int prev_val)
655 scr_printf("%s ", prompt);
658 color(BRIGHT_MAGENTA);
659 scr_printf("%s", (prev_val ? "Yes" : "No"));
663 r = (yesno_d(prev_val));
669 * intprompt() - like strprompt(), except for an integer
670 * (note that it RETURNS the new value!)
672 int intprompt(char *prompt, int ival, int imin, int imax)
680 snprintf(buf, sizeof buf, "%d", i);
681 strprompt(prompt, buf, 15);
683 for (p=0; p<strlen(buf); ++p) {
684 if ( (!isdigit(buf[p]))
685 && ( (buf[p]!='-') || (p!=0) ) )
689 scr_printf("*** Must be no less than %d.\n", imin);
691 scr_printf("*** Must be no more than %d.\n", imax);
692 } while ((i < imin) || (i > imax));
697 * newprompt() - prompt for a string with no existing value
698 * (clears out string buffer first)
700 void newprompt(char *prompt, char *str, int len)
702 color(BRIGHT_MAGENTA);
703 scr_printf("%s", prompt);
711 { /* returns a lower case value */
720 * parse the citadel.rc file
722 void load_command_set(void)
726 char editor_key[100];
728 struct citcmd *lastcmd = NULL;
734 /* first, set up some defaults for non-required variables */
736 for (i = 0; i < MAX_EDITORS; i++)
737 strcpy(editor_paths[i], "");
738 strcpy(printcmd, "");
739 strcpy(rc_username, "");
740 strcpy(rc_password, "");
743 rc_allow_attachments = 0;
744 rc_remember_passwords = 0;
745 strcpy(rc_exp_cmd, "");
746 rc_display_message_numbers = 0;
747 rc_force_mail_prompts = 0;
750 strcpy(rc_url_cmd, "");
751 strcpy(rc_gotmail_cmd, "");
753 rc_encrypt = RC_DEFAULT;
755 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
756 rc_screen = RC_DEFAULT;
758 rc_alt_semantics = 0;
760 /* now try to open the citadel.rc file */
763 if (getenv("HOME") != NULL) {
764 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
765 ccfile = fopen(buf, "r");
767 if (ccfile == NULL) {
768 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
769 ccfile = fopen(buf, "r");
771 if (ccfile == NULL) {
772 ccfile = fopen("/etc/citadel.rc", "r");
774 if (ccfile == NULL) {
775 ccfile = fopen("./citadel.rc", "r");
777 if (ccfile == NULL) {
778 perror("commands: cannot open citadel.rc");
781 while (fgets(buf, sizeof buf, ccfile) != NULL) {
782 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
783 buf[strlen(buf) - 1] = 0;
785 if (!strncasecmp(buf, "encrypt=", 8)) {
786 if (!strcasecmp(&buf[8], "yes")) {
790 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
795 else if (!strcasecmp(&buf[8], "no")) {
798 else if (!strcasecmp(&buf[8], "default")) {
799 rc_encrypt = RC_DEFAULT;
804 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
805 if (!strncasecmp(buf, "fullscreen=", 11)) {
806 if (!strcasecmp(&buf[11], "yes"))
808 else if (!strcasecmp(&buf[11], "no"))
813 if (!strncasecmp(buf, "editor=", 7))
814 strcpy(editor_paths[0], &buf[7]);
816 for (i = 0; i < MAX_EDITORS; i++)
818 sprintf(editor_key, "editor%d=", i);
819 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
820 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
823 if (!strncasecmp(buf, "printcmd=", 9))
824 strcpy(printcmd, &buf[9]);
826 if (!strncasecmp(buf, "expcmd=", 7))
827 strcpy(rc_exp_cmd, &buf[7]);
829 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
830 have_xterm = (char) atoi(&buf[24]);
832 if (!strncasecmp(buf, "use_floors=", 11)) {
833 if (!strcasecmp(&buf[11], "yes"))
834 rc_floor_mode = RC_YES;
835 if (!strcasecmp(&buf[11], "no"))
836 rc_floor_mode = RC_NO;
837 if (!strcasecmp(&buf[11], "default"))
838 rc_floor_mode = RC_DEFAULT;
840 if (!strncasecmp(buf, "beep=", 5)) {
841 rc_exp_beep = atoi(&buf[5]);
843 if (!strncasecmp(buf, "allow_attachments=", 18)) {
844 rc_allow_attachments = atoi(&buf[18]);
846 if (!strncasecmp(buf, "idle_threshold=", 15)) {
847 rc_idle_threshold = atoi(&buf[15]);
849 if (!strncasecmp(buf, "remember_passwords=", 19)) {
850 rc_remember_passwords = atoi(&buf[19]);
852 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
853 rc_display_message_numbers = atoi(&buf[24]);
855 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
856 rc_force_mail_prompts = atoi(&buf[19]);
858 if (!strncasecmp(buf, "ansi_color=", 11)) {
859 if (!strncasecmp(&buf[11], "on", 2))
861 if (!strncasecmp(&buf[11], "auto", 4))
862 rc_ansi_color = 2; /* autodetect */
863 if (!strncasecmp(&buf[11], "user", 4))
864 rc_ansi_color = 3; /* user config */
866 if (!strncasecmp(buf, "use_background=", 15)) {
867 if (!strncasecmp(&buf[15], "on", 2))
870 if (!strncasecmp(buf, "prompt_control=", 15)) {
871 if (!strncasecmp(&buf[15], "on", 2))
872 rc_prompt_control = 1;
873 if (!strncasecmp(&buf[15], "user", 4))
874 rc_prompt_control = 3; /* user config */
876 if (!strncasecmp(buf, "username=", 9))
877 strcpy(rc_username, &buf[9]);
879 if (!strncasecmp(buf, "password=", 9))
880 strcpy(rc_password, &buf[9]);
882 if (!strncasecmp(buf, "urlcmd=", 7))
883 strcpy(rc_url_cmd, &buf[7]);
885 if (!strncasecmp(buf, "gotmailcmd=", 11))
886 strcpy(rc_gotmail_cmd, &buf[11]);
888 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
889 if (!strncasecmp(&buf[20], "yes", 3)) {
890 rc_alt_semantics = 1;
893 rc_alt_semantics = 0;
896 if (!strncasecmp(buf, "cmd=", 4)) {
897 strcpy(buf, &buf[4]);
899 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
901 cptr->c_cmdnum = atoi(buf);
902 for (d = strlen(buf); d >= 0; --d)
905 strcpy(buf, &buf[b + 1]);
907 cptr->c_axlevel = atoi(buf);
908 for (d = strlen(buf); d >= 0; --d)
911 strcpy(buf, &buf[b + 1]);
913 for (a = 0; a < 5; ++a)
914 cptr->c_keys[a][0] = 0;
918 buf[strlen(buf) + 1] = 0;
919 while (strlen(buf) > 0) {
921 for (d = strlen(buf); d >= 0; --d)
924 strncpy(cptr->c_keys[a], buf, b);
925 cptr->c_keys[a][b] = 0;
927 strcpy(buf, &buf[b + 1]);
937 lastcmd->next = cptr;
947 * return the key associated with a command
949 char keycmd(char *cmdstr)
953 for (a = 0; a < strlen(cmdstr); ++a)
954 if (cmdstr[a] == '&')
955 return (tolower(cmdstr[a + 1]));
961 * Output the string from a key command without the ampersand
962 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
964 char *cmd_expand(char *strbuf, int mode)
972 for (a = 0; a < strlen(exp); ++a) {
973 if (strbuf[a] == '&') {
976 strcpy(&exp[a], &exp[a + 1]);
980 strcpy(buf, &exp[a + 2]);
986 if (!strncmp(&exp[a], "^r", 2)) {
988 strcpy(&exp[a], room_name);
989 strcat(exp, &buf[a + 2]);
991 if (!strncmp(&exp[a], "^c", 2)) {
993 strcpy(&exp[a + 1], &exp[a + 2]);
1003 * Comparison function to determine if entered commands match a
1004 * command loaded from the config file.
1006 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1017 for (a = 0; a < ncomp; ++a) {
1018 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1019 || (cptr->c_axlevel > cmdax))
1027 * This function returns 1 if a given command requires a string input
1029 int requires_string(struct citcmd *cptr, int ncomp)
1034 strcpy(buf, cptr->c_keys[ncomp - 1]);
1035 for (a = 0; a < strlen(buf); ++a) {
1044 * Input a command at the main prompt.
1045 * This function returns an integer command number. If the command prompts
1046 * for a string then it is placed in the supplied buffer.
1048 int getcmd(CtdlIPC *ipc, char *argbuf)
1057 struct citcmd *cptr;
1060 * Starting a new command now, so set sigcaught to 0. This variable
1061 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1062 * been interrupted by a keypress.
1066 /* Switch color support on or off if we're in user mode */
1067 if (rc_ansi_color == 3) {
1068 if (userflags & US_COLOR)
1073 /* if we're running in idiot mode, display a cute little menu */
1074 IFNEXPERT formout(ipc, "mainmenu");
1079 for (a = 0; a < 5; ++a)
1081 /* now the room prompt... */
1082 ok_to_interrupt = 1;
1083 color(BRIGHT_WHITE);
1084 scr_printf("\n%s", room_name);
1086 scr_printf("%c ", room_prompt(room_flags));
1091 ok_to_interrupt = 0;
1093 /* Handle the backspace key, but only if there's something
1094 * to backspace over...
1096 if ((ch == 8) && (cmdpos > 0)) {
1097 back(cmdspaces[cmdpos - 1] + 1);
1101 /* Spacebar invokes "lazy traversal" commands */
1102 if ((ch == 32) && (cmdpos == 0)) {
1103 this_lazy_cmd = next_lazy_cmd;
1104 if (this_lazy_cmd == 13)
1106 if (this_lazy_cmd == 5)
1108 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1109 if (cptr->c_cmdnum == this_lazy_cmd) {
1110 for (a = 0; a < 5; ++a)
1111 if (cptr->c_keys[a][0] != 0)
1112 scr_printf("%s ", cmd_expand(
1113 cptr->c_keys[a], 0));
1115 return (this_lazy_cmd);
1119 return (this_lazy_cmd);
1121 /* Otherwise, process the command */
1122 cmdbuf[cmdpos] = tolower(ch);
1124 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1125 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1127 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1128 cmdspaces[cmdpos] = strlen(
1129 cmd_expand(cptr->c_keys[cmdpos], 0));
1131 if ((cptr->c_keys[cmdpos + 1]) != 0)
1137 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1138 if (cmdmatch(cmdbuf, cptr, 5)) {
1139 /* We've found our command. */
1140 if (requires_string(cptr, cmdpos)) {
1141 getline(argbuf, 32);
1146 /* If this command is one that changes rooms,
1147 * then the next lazy-command (space bar)
1148 * should be "read new" instead of "goto"
1150 if ((cptr->c_cmdnum == 5)
1151 || (cptr->c_cmdnum == 6)
1152 || (cptr->c_cmdnum == 47)
1153 || (cptr->c_cmdnum == 52)
1154 || (cptr->c_cmdnum == 16)
1155 || (cptr->c_cmdnum == 20))
1158 /* If this command is "read new"
1159 * then the next lazy-command (space bar)
1162 if (cptr->c_cmdnum == 13)
1165 return (cptr->c_cmdnum);
1171 pprintf("\rOne of ... \n");
1172 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1173 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1174 for (a = 0; a < 5; ++a) {
1175 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1181 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1183 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1184 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1185 for (a = 0; a < cmdpos; ++a) {
1187 cmd_expand(cptr->c_keys[a], 0));
1202 * set tty modes. commands are:
1204 * 01- set to bbs mode
1205 * 2 - save current settings for later restoral
1206 * 3 - restore saved settings
1208 #ifdef HAVE_TERMIOS_H
1209 void sttybbs(int cmd)
1210 { /* SysV version of sttybbs() */
1211 struct termios live;
1212 static struct termios saved_settings;
1213 static int last_cmd = 0;
1220 if ((cmd == 0) || (cmd == 1)) {
1221 tcgetattr(0, &live);
1222 live.c_iflag = ISTRIP | IXON | IXANY;
1223 live.c_oflag = OPOST | ONLCR;
1224 live.c_lflag = ISIG | NOFLSH;
1226 live.c_cc[VINTR] = 0;
1227 live.c_cc[VQUIT] = 0;
1230 live.c_cc[VMIN] = 0;
1231 live.c_cc[VTIME] = 0;
1234 /* do we even need this stuff anymore? */
1235 /* live.c_line=0; */
1236 live.c_cc[VERASE] = 8;
1237 live.c_cc[VKILL] = 24;
1238 live.c_cc[VEOF] = 1;
1239 live.c_cc[VEOL] = 255;
1240 live.c_cc[VEOL2] = 0;
1241 live.c_cc[VSTART] = 0;
1242 tcsetattr(0, TCSADRAIN, &live);
1245 tcgetattr(0, &saved_settings);
1248 tcsetattr(0, TCSADRAIN, &saved_settings);
1253 void sttybbs(int cmd)
1254 { /* BSD version of sttybbs() */
1256 static struct sgttyb saved_settings;
1257 static int last_cmd = 0;
1264 if ((cmd == 0) || (cmd == 1)) {
1266 live.sg_flags |= CBREAK;
1267 live.sg_flags |= CRMOD;
1268 live.sg_flags |= NL1;
1269 live.sg_flags &= ~ECHO;
1271 live.sg_flags |= NOFLSH;
1275 gtty(0, &saved_settings);
1278 stty(0, &saved_settings);
1285 * display_help() - help file viewer
1287 void display_help(CtdlIPC *ipc, char *name)
1294 * fmout() - Citadel text formatter and paginator
1297 int width, /* screen width to use */
1298 FILE *fpin, /* file to read from, or NULL to format given text */
1299 char *text, /* text to be formatted (when fpin is NULL */
1300 FILE *fpout, /* file to write to, or NULL to write to screen */
1301 char pagin, /* nonzero if we should use the paginator */
1302 int height, /* screen height to use */
1303 int starting_lp,/* starting value for lines_printed, -1 for global */
1304 int subst) /* nonzero if we should use hypertext mode */
1306 char *buffer = NULL; /* The current message */
1307 char *word = NULL; /* What we are about to actually print */
1308 char *e; /* Pointer to position in text */
1309 char old = 0; /* The previous character */
1310 int column = 0; /* Current column */
1311 size_t i; /* Generic counter */
1313 /* Space for a single word, which can be at most screenwidth */
1314 word = (char *)calloc(1, width);
1316 err_printf("Can't alloc memory to print message: %s!\n",
1321 /* Read the entire message body into memory */
1323 buffer = load_message_from_file(fpin);
1325 err_printf("Can't print message: %s!\n",
1334 if (starting_lp >= 0)
1335 lines_printed = starting_lp;
1337 /* Run the message body */
1339 /* Catch characters that shouldn't be there at all */
1344 /* First, are we looking at a newline? */
1347 if (*e == ' ') { /* Paragraph */
1349 fprintf(fpout, "\n");
1353 lines_printed = checkpagin(lines_printed, pagin, height);
1356 } else if (old != ' ') {/* Don't print two spaces */
1358 fprintf(fpout, " ");
1367 /* Are we looking at a nonprintable? */
1368 if ( (*e < 32) || (*e > 126) ) {
1372 /* Or are we looking at a space? */
1375 if (column >= width - 1) {
1376 /* Are we in the rightmost column? */
1378 fprintf(fpout, "\n");
1382 lines_printed = checkpagin(lines_printed, pagin, height);
1385 } else if (!(column == 0 && old == ' ')) {
1386 /* Eat only the first space on a line */
1388 fprintf(fpout, " ");
1394 /* ONLY eat the FIRST space on a line */
1400 /* Read a word, slightly messy */
1403 if (!isprint(e[i]) && !isspace(e[i]))
1410 /* We should never see these, but... slightly messy */
1411 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1414 /* Break up really long words */
1415 /* TODO: auto-hyphenation someday? */
1418 strncpy(word, e, i);
1421 /* Decide where to print the word */
1422 if (column + i >= width) {
1423 /* Wrap to the next line */
1425 fprintf(fpout, "\n");
1429 lines_printed = checkpagin(lines_printed, pagin, height);
1434 /* Print the word */
1436 fprintf(fpout, "%s", word);
1438 scr_printf("%s", word);
1441 e += i; /* Start over with the whitepsace! */
1445 if (fpin) /* We allocated this, remember? */
1448 /* Is this necessary? It makes the output kind of spacey. */
1450 fprintf(fpout, "\n");
1454 lines_printed = checkpagin(lines_printed, pagin, height);
1462 * support ANSI color if defined
1464 void color(int colornum)
1466 static int hold_color;
1467 static int current_color;
1469 if (colornum == COLOR_PUSH) {
1470 hold_color = current_color;
1474 if (colornum == COLOR_POP) {
1479 current_color = colornum;
1481 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1482 if (scr_color(colornum))
1485 /* When switching to dim white, actually output an 'original
1486 * pair' sequence -- this looks better on black-on-white
1487 * terminals. - Changed to ORIGINAL_PAIR as this actually
1488 * wound up looking horrible on black-on-white terminals, not
1489 * to mention transparent terminals.
1491 if (colornum == ORIGINAL_PAIR)
1492 printf("\033[0;39;49m");
1494 printf("\033[%d;3%d;4%dm",
1495 (colornum & 8) ? 1 : 0,
1503 void cls(int colornum)
1506 printf("\033[4%dm\033[2J\033[H\033[0m",
1507 colornum ? colornum : rc_color_use_bg);
1514 * Detect whether ANSI color is available (answerback)
1516 void send_ansi_detect(void)
1518 if (rc_ansi_color == 2) {
1525 void look_for_ansi(void)
1533 if (rc_ansi_color == 0) {
1535 } else if (rc_ansi_color == 1) {
1537 } else if (rc_ansi_color == 2) {
1539 /* otherwise, do the auto-detect */
1544 if ((now - AnsiDetect) < 2)
1553 select(1, &rfds, NULL, NULL, &tv);
1554 if (FD_ISSET(0, &rfds)) {
1555 abuf[strlen(abuf) + 1] = 0;
1556 read(0, &abuf[strlen(abuf)], 1);
1558 } while (FD_ISSET(0, &rfds));
1560 for (a = 0; a < strlen(abuf); ++a) {
1561 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1562 && (abuf[a + 2] == '?')) {
1571 * Display key options (highlight hotkeys inside angle brackets)
1573 void keyopt(char *buf) {
1577 for (i=0; i<strlen(buf); ++i) {
1580 color(BRIGHT_MAGENTA);
1594 * Present a key-menu line choice type of thing
1596 char keymenu(char *menuprompt, char *menustring) {
1602 int display_prompt = 1;
1604 choices = num_tokens(menustring, '|');
1606 if (menuprompt != NULL) do_prompt = 1;
1607 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1610 if (display_prompt) {
1612 scr_printf("%s ", menuprompt);
1615 for (i=0; i<choices; ++i) {
1616 extract(buf, menustring, i);
1626 if ( (do_prompt) && (ch=='?') ) {
1627 scr_printf("\rOne of... ");
1629 for (i=0; i<choices; ++i) {
1630 extract(buf, menustring, i);
1639 for (i=0; i<choices; ++i) {
1640 extract(buf, menustring, i);
1641 for (c=1; c<strlen(buf); ++c) {
1642 if ( (ch == tolower(buf[c]))
1644 && (buf[c+1]=='>') ) {
1645 for (a=0; a<strlen(buf); ++a) {
1646 if ( (a!=(c-1)) && (a!=(c+1))) {