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);
160 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
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(ipc_for_signal_handlers->ServInfo.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(ipc_for_signal_handlers->ServInfo.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 && (ipc_for_signal_handlers->ServInfo.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(ipc_for_signal_handlers);
483 scr_set_windowsize(ipc_for_signal_handlers);
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_BLOCK);
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(imagecmd, "");
740 strcpy(rc_username, "");
741 strcpy(rc_password, "");
744 rc_allow_attachments = 0;
745 rc_remember_passwords = 0;
746 strcpy(rc_exp_cmd, "");
747 rc_display_message_numbers = 0;
748 rc_force_mail_prompts = 0;
751 rc_reply_extedit = 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, "imagecmd=", 9))
829 strcpy(imagecmd, &buf[9]);
831 if (!strncasecmp(buf, "expcmd=", 7))
832 strcpy(rc_exp_cmd, &buf[7]);
834 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
835 have_xterm = (char) atoi(&buf[24]);
837 if (!strncasecmp(buf, "use_floors=", 11)) {
838 if (!strcasecmp(&buf[11], "yes"))
839 rc_floor_mode = RC_YES;
840 if (!strcasecmp(&buf[11], "no"))
841 rc_floor_mode = RC_NO;
842 if (!strcasecmp(&buf[11], "default"))
843 rc_floor_mode = RC_DEFAULT;
845 if (!strncasecmp(buf, "beep=", 5)) {
846 rc_exp_beep = atoi(&buf[5]);
848 if (!strncasecmp(buf, "allow_attachments=", 18)) {
849 rc_allow_attachments = atoi(&buf[18]);
851 if (!strncasecmp(buf, "idle_threshold=", 15)) {
852 rc_idle_threshold = atol(&buf[15]);
854 if (!strncasecmp(buf, "remember_passwords=", 19)) {
855 rc_remember_passwords = atoi(&buf[19]);
857 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
858 rc_display_message_numbers = atoi(&buf[24]);
860 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
861 rc_force_mail_prompts = atoi(&buf[19]);
863 if (!strncasecmp(buf, "ansi_color=", 11)) {
864 if (!strncasecmp(&buf[11], "on", 2))
866 if (!strncasecmp(&buf[11], "auto", 4))
867 rc_ansi_color = 2; /* autodetect */
868 if (!strncasecmp(&buf[11], "user", 4))
869 rc_ansi_color = 3; /* user config */
871 if (!strncasecmp(buf, "use_background=", 15)) {
872 if (!strncasecmp(&buf[15], "on", 2))
875 if (!strncasecmp(buf, "prompt_control=", 15)) {
876 if (!strncasecmp(&buf[15], "on", 2))
877 rc_prompt_control = 1;
878 if (!strncasecmp(&buf[15], "user", 4))
879 rc_prompt_control = 3; /* user config */
881 if (!strncasecmp(buf, "username=", 9))
882 strcpy(rc_username, &buf[9]);
884 if (!strncasecmp(buf, "password=", 9))
885 strcpy(rc_password, &buf[9]);
887 if (!strncasecmp(buf, "urlcmd=", 7))
888 strcpy(rc_url_cmd, &buf[7]);
890 if (!strncasecmp(buf, "gotmailcmd=", 11))
891 strcpy(rc_gotmail_cmd, &buf[11]);
893 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
894 if (!strncasecmp(&buf[20], "yes", 3)) {
895 rc_alt_semantics = 1;
898 rc_alt_semantics = 0;
901 if (!strncasecmp(buf, "reply_with_external_editor=", 27)) {
902 if (!strncasecmp(&buf[27], "yes", 3)) {
903 rc_reply_extedit = 1;
906 rc_reply_extedit = 0;
909 if (!strncasecmp(buf, "cmd=", 4)) {
910 strcpy(buf, &buf[4]);
912 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
914 cptr->c_cmdnum = atoi(buf);
915 for (d = strlen(buf); d >= 0; --d)
918 strcpy(buf, &buf[b + 1]);
920 cptr->c_axlevel = atoi(buf);
921 for (d = strlen(buf); d >= 0; --d)
924 strcpy(buf, &buf[b + 1]);
926 for (a = 0; a < 5; ++a)
927 cptr->c_keys[a][0] = 0;
931 buf[strlen(buf) + 1] = 0;
932 while (strlen(buf) > 0) {
934 for (d = strlen(buf); d >= 0; --d)
937 strncpy(cptr->c_keys[a], buf, b);
938 cptr->c_keys[a][b] = 0;
940 strcpy(buf, &buf[b + 1]);
950 lastcmd->next = cptr;
960 * return the key associated with a command
962 char keycmd(char *cmdstr)
966 for (a = 0; a < strlen(cmdstr); ++a)
967 if (cmdstr[a] == '&')
968 return (tolower(cmdstr[a + 1]));
974 * Output the string from a key command without the ampersand
975 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
977 char *cmd_expand(char *strbuf, int mode)
985 for (a = 0; a < strlen(exp); ++a) {
986 if (strbuf[a] == '&') {
989 strcpy(&exp[a], &exp[a + 1]);
993 strcpy(buf, &exp[a + 2]);
999 if (!strncmp(&exp[a], "^r", 2)) {
1001 strcpy(&exp[a], room_name);
1002 strcat(exp, &buf[a + 2]);
1004 if (!strncmp(&exp[a], "^c", 2)) {
1006 strcpy(&exp[a + 1], &exp[a + 2]);
1016 * Comparison function to determine if entered commands match a
1017 * command loaded from the config file.
1019 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1030 for (a = 0; a < ncomp; ++a) {
1031 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1032 || (cptr->c_axlevel > cmdax))
1040 * This function returns 1 if a given command requires a string input
1042 int requires_string(struct citcmd *cptr, int ncomp)
1047 strcpy(buf, cptr->c_keys[ncomp - 1]);
1048 for (a = 0; a < strlen(buf); ++a) {
1057 * Input a command at the main prompt.
1058 * This function returns an integer command number. If the command prompts
1059 * for a string then it is placed in the supplied buffer.
1061 int getcmd(CtdlIPC *ipc, char *argbuf)
1070 struct citcmd *cptr;
1073 * Starting a new command now, so set sigcaught to 0. This variable
1074 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1075 * been interrupted by a keypress.
1079 /* Switch color support on or off if we're in user mode */
1080 if (rc_ansi_color == 3) {
1081 if (userflags & US_COLOR)
1086 /* if we're running in idiot mode, display a cute little menu */
1087 IFNEXPERT formout(ipc, "mainmenu");
1092 for (a = 0; a < 5; ++a)
1094 /* now the room prompt... */
1095 ok_to_interrupt = 1;
1096 color(BRIGHT_WHITE);
1097 scr_printf("\n%s", room_name);
1099 scr_printf("%c ", room_prompt(room_flags));
1104 ok_to_interrupt = 0;
1106 /* Handle the backspace key, but only if there's something
1107 * to backspace over...
1109 if ((ch == 8) && (cmdpos > 0)) {
1110 back(cmdspaces[cmdpos - 1] + 1);
1114 /* Spacebar invokes "lazy traversal" commands */
1115 if ((ch == 32) && (cmdpos == 0)) {
1116 this_lazy_cmd = next_lazy_cmd;
1117 if (this_lazy_cmd == 13)
1119 if (this_lazy_cmd == 5)
1121 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1122 if (cptr->c_cmdnum == this_lazy_cmd) {
1123 for (a = 0; a < 5; ++a)
1124 if (cptr->c_keys[a][0] != 0)
1125 scr_printf("%s ", cmd_expand(
1126 cptr->c_keys[a], 0));
1128 return (this_lazy_cmd);
1132 return (this_lazy_cmd);
1134 /* Otherwise, process the command */
1135 cmdbuf[cmdpos] = tolower(ch);
1137 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1138 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1140 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1141 cmdspaces[cmdpos] = strlen(
1142 cmd_expand(cptr->c_keys[cmdpos], 0));
1144 if ((cptr->c_keys[cmdpos + 1]) != 0)
1150 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1151 if (cmdmatch(cmdbuf, cptr, 5)) {
1152 /* We've found our command. */
1153 if (requires_string(cptr, cmdpos)) {
1154 getline(argbuf, 32);
1159 /* If this command is one that changes rooms,
1160 * then the next lazy-command (space bar)
1161 * should be "read new" instead of "goto"
1163 if ((cptr->c_cmdnum == 5)
1164 || (cptr->c_cmdnum == 6)
1165 || (cptr->c_cmdnum == 47)
1166 || (cptr->c_cmdnum == 52)
1167 || (cptr->c_cmdnum == 16)
1168 || (cptr->c_cmdnum == 20))
1171 /* If this command is "read new"
1172 * then the next lazy-command (space bar)
1175 if (cptr->c_cmdnum == 13)
1178 return (cptr->c_cmdnum);
1184 pprintf("\rOne of ... \n");
1185 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1186 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1187 for (a = 0; a < 5; ++a) {
1188 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1194 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1196 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1197 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1198 for (a = 0; a < cmdpos; ++a) {
1200 cmd_expand(cptr->c_keys[a], 0));
1215 * set tty modes. commands are:
1217 * 01- set to bbs mode
1218 * 2 - save current settings for later restoral
1219 * 3 - restore saved settings
1221 #ifdef HAVE_TERMIOS_H
1222 void sttybbs(int cmd)
1223 { /* SysV version of sttybbs() */
1224 struct termios live;
1225 static struct termios saved_settings;
1226 static int last_cmd = 0;
1233 if ((cmd == 0) || (cmd == 1)) {
1234 tcgetattr(0, &live);
1235 live.c_iflag = ISTRIP | IXON | IXANY;
1236 live.c_oflag = OPOST | ONLCR;
1237 live.c_lflag = ISIG | NOFLSH;
1239 live.c_cc[VINTR] = 0;
1240 live.c_cc[VQUIT] = 0;
1243 live.c_cc[VMIN] = 0;
1244 live.c_cc[VTIME] = 0;
1247 /* do we even need this stuff anymore? */
1248 /* live.c_line=0; */
1249 live.c_cc[VERASE] = 8;
1250 live.c_cc[VKILL] = 24;
1251 live.c_cc[VEOF] = 1;
1252 live.c_cc[VEOL] = 255;
1253 live.c_cc[VEOL2] = 0;
1254 live.c_cc[VSTART] = 0;
1255 tcsetattr(0, TCSADRAIN, &live);
1258 tcgetattr(0, &saved_settings);
1261 tcsetattr(0, TCSADRAIN, &saved_settings);
1266 void sttybbs(int cmd)
1267 { /* BSD version of sttybbs() */
1269 static struct sgttyb saved_settings;
1270 static int last_cmd = 0;
1277 if ((cmd == 0) || (cmd == 1)) {
1279 live.sg_flags |= CBREAK;
1280 live.sg_flags |= CRMOD;
1281 live.sg_flags |= NL1;
1282 live.sg_flags &= ~ECHO;
1284 live.sg_flags |= NOFLSH;
1288 gtty(0, &saved_settings);
1291 stty(0, &saved_settings);
1298 * display_help() - help file viewer
1300 void display_help(CtdlIPC *ipc, char *name)
1307 * fmout() - Citadel text formatter and paginator
1310 int width, /* screen width to use */
1311 FILE *fpin, /* file to read from, or NULL to format given text */
1312 char *text, /* text to be formatted (when fpin is NULL */
1313 FILE *fpout, /* file to write to, or NULL to write to screen */
1314 char pagin, /* nonzero if we should use the paginator */
1315 int height, /* screen height to use */
1316 int starting_lp,/* starting value for lines_printed, -1 for global */
1317 int subst) /* nonzero if we should use hypertext mode */
1319 char *buffer = NULL; /* The current message */
1320 char *word = NULL; /* What we are about to actually print */
1321 char *e; /* Pointer to position in text */
1322 char old = 0; /* The previous character */
1323 int column = 0; /* Current column */
1324 size_t i; /* Generic counter */
1326 /* Space for a single word, which can be at most screenwidth */
1327 word = (char *)calloc(1, width);
1329 err_printf("Can't alloc memory to print message: %s!\n",
1334 /* Read the entire message body into memory */
1336 buffer = load_message_from_file(fpin);
1338 err_printf("Can't print message: %s!\n",
1347 if (starting_lp >= 0)
1348 lines_printed = starting_lp;
1350 /* Run the message body */
1352 /* Catch characters that shouldn't be there at all */
1357 /* First, are we looking at a newline? */
1360 if (*e == ' ') { /* Paragraph */
1362 fprintf(fpout, "\n");
1366 lines_printed = checkpagin(lines_printed, pagin, height);
1369 } else if (old != ' ') {/* Don't print two spaces */
1371 fprintf(fpout, " ");
1380 /* Are we looking at a nonprintable? */
1381 if ( (*e < 32) || (*e > 126) ) {
1385 /* Or are we looking at a space? */
1388 if (column >= width - 1) {
1389 /* Are we in the rightmost column? */
1391 fprintf(fpout, "\n");
1395 lines_printed = checkpagin(lines_printed, pagin, height);
1398 } else if (!(column == 0 && old == ' ')) {
1399 /* Eat only the first space on a line */
1401 fprintf(fpout, " ");
1407 /* ONLY eat the FIRST space on a line */
1413 /* Read a word, slightly messy */
1416 if (!isprint(e[i]) && !isspace(e[i]))
1423 /* We should never see these, but... slightly messy */
1424 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1427 /* Break up really long words */
1428 /* TODO: auto-hyphenation someday? */
1431 strncpy(word, e, i);
1434 /* Decide where to print the word */
1435 if (column + i >= width) {
1436 /* Wrap to the next line */
1438 fprintf(fpout, "\n");
1442 lines_printed = checkpagin(lines_printed, pagin, height);
1447 /* Print the word */
1449 fprintf(fpout, "%s", word);
1451 scr_printf("%s", word);
1454 e += i; /* Start over with the whitepsace! */
1458 if (fpin) /* We allocated this, remember? */
1461 /* Is this necessary? It makes the output kind of spacey. */
1463 fprintf(fpout, "\n");
1467 lines_printed = checkpagin(lines_printed, pagin, height);
1475 * support ANSI color if defined
1477 void color(int colornum)
1479 static int hold_color;
1480 static int current_color;
1482 if (colornum == COLOR_PUSH) {
1483 hold_color = current_color;
1487 if (colornum == COLOR_POP) {
1492 current_color = colornum;
1494 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1495 if (scr_color(colornum))
1498 /* When switching to dim white, actually output an 'original
1499 * pair' sequence -- this looks better on black-on-white
1500 * terminals. - Changed to ORIGINAL_PAIR as this actually
1501 * wound up looking horrible on black-on-white terminals, not
1502 * to mention transparent terminals.
1504 if (colornum == ORIGINAL_PAIR)
1505 printf("\033[0;39;49m");
1507 printf("\033[%d;3%d;4%dm",
1508 (colornum & 8) ? 1 : 0,
1516 void cls(int colornum)
1519 printf("\033[4%dm\033[2J\033[H\033[0m",
1520 colornum ? colornum : rc_color_use_bg);
1527 * Detect whether ANSI color is available (answerback)
1529 void send_ansi_detect(void)
1531 if (rc_ansi_color == 2) {
1538 void look_for_ansi(void)
1546 if (rc_ansi_color == 0) {
1548 } else if (rc_ansi_color == 1) {
1550 } else if (rc_ansi_color == 2) {
1552 /* otherwise, do the auto-detect */
1557 if ((now - AnsiDetect) < 2)
1566 select(1, &rfds, NULL, NULL, &tv);
1567 if (FD_ISSET(0, &rfds)) {
1568 abuf[strlen(abuf) + 1] = 0;
1569 read(0, &abuf[strlen(abuf)], 1);
1571 } while (FD_ISSET(0, &rfds));
1573 for (a = 0; a < strlen(abuf); ++a) {
1574 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1575 && (abuf[a + 2] == '?')) {
1584 * Display key options (highlight hotkeys inside angle brackets)
1586 void keyopt(char *buf) {
1590 for (i=0; i<strlen(buf); ++i) {
1593 color(BRIGHT_MAGENTA);
1607 * Present a key-menu line choice type of thing
1609 char keymenu(char *menuprompt, char *menustring) {
1615 int display_prompt = 1;
1617 choices = num_tokens(menustring, '|');
1619 if (menuprompt != NULL) do_prompt = 1;
1620 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1623 if (display_prompt) {
1625 scr_printf("%s ", menuprompt);
1628 for (i=0; i<choices; ++i) {
1629 extract(buf, menustring, i);
1639 if ( (do_prompt) && (ch=='?') ) {
1640 scr_printf("\rOne of... ");
1642 for (i=0; i<choices; ++i) {
1643 extract(buf, menustring, i);
1652 for (i=0; i<choices; ++i) {
1653 extract(buf, menustring, i);
1654 for (c=1; c<strlen(buf); ++c) {
1655 if ( (ch == tolower(buf[c]))
1657 && (buf[c+1]=='>') ) {
1658 for (a=0; a<strlen(buf); ++a) {
1659 if ( (a!=(c-1)) && (a!=(c+1))) {