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 rc_reply_extedit = 0;
751 strcpy(rc_url_cmd, "");
752 strcpy(rc_gotmail_cmd, "");
754 rc_encrypt = RC_DEFAULT;
756 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
757 rc_screen = RC_DEFAULT;
759 rc_alt_semantics = 0;
761 /* now try to open the citadel.rc file */
764 if (getenv("HOME") != NULL) {
765 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
766 ccfile = fopen(buf, "r");
768 if (ccfile == NULL) {
769 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
770 ccfile = fopen(buf, "r");
772 if (ccfile == NULL) {
773 ccfile = fopen("/etc/citadel.rc", "r");
775 if (ccfile == NULL) {
776 ccfile = fopen("./citadel.rc", "r");
778 if (ccfile == NULL) {
779 perror("commands: cannot open citadel.rc");
782 while (fgets(buf, sizeof buf, ccfile) != NULL) {
783 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
784 buf[strlen(buf) - 1] = 0;
786 if (!strncasecmp(buf, "encrypt=", 8)) {
787 if (!strcasecmp(&buf[8], "yes")) {
791 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
796 else if (!strcasecmp(&buf[8], "no")) {
799 else if (!strcasecmp(&buf[8], "default")) {
800 rc_encrypt = RC_DEFAULT;
805 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
806 if (!strncasecmp(buf, "fullscreen=", 11)) {
807 if (!strcasecmp(&buf[11], "yes"))
809 else if (!strcasecmp(&buf[11], "no"))
814 if (!strncasecmp(buf, "editor=", 7))
815 strcpy(editor_paths[0], &buf[7]);
817 for (i = 0; i < MAX_EDITORS; i++)
819 sprintf(editor_key, "editor%d=", i);
820 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
821 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
824 if (!strncasecmp(buf, "printcmd=", 9))
825 strcpy(printcmd, &buf[9]);
827 if (!strncasecmp(buf, "expcmd=", 7))
828 strcpy(rc_exp_cmd, &buf[7]);
830 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
831 have_xterm = (char) atoi(&buf[24]);
833 if (!strncasecmp(buf, "use_floors=", 11)) {
834 if (!strcasecmp(&buf[11], "yes"))
835 rc_floor_mode = RC_YES;
836 if (!strcasecmp(&buf[11], "no"))
837 rc_floor_mode = RC_NO;
838 if (!strcasecmp(&buf[11], "default"))
839 rc_floor_mode = RC_DEFAULT;
841 if (!strncasecmp(buf, "beep=", 5)) {
842 rc_exp_beep = atoi(&buf[5]);
844 if (!strncasecmp(buf, "allow_attachments=", 18)) {
845 rc_allow_attachments = atoi(&buf[18]);
847 if (!strncasecmp(buf, "idle_threshold=", 15)) {
848 rc_idle_threshold = atoi(&buf[15]);
850 if (!strncasecmp(buf, "remember_passwords=", 19)) {
851 rc_remember_passwords = atoi(&buf[19]);
853 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
854 rc_display_message_numbers = atoi(&buf[24]);
856 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
857 rc_force_mail_prompts = atoi(&buf[19]);
859 if (!strncasecmp(buf, "ansi_color=", 11)) {
860 if (!strncasecmp(&buf[11], "on", 2))
862 if (!strncasecmp(&buf[11], "auto", 4))
863 rc_ansi_color = 2; /* autodetect */
864 if (!strncasecmp(&buf[11], "user", 4))
865 rc_ansi_color = 3; /* user config */
867 if (!strncasecmp(buf, "use_background=", 15)) {
868 if (!strncasecmp(&buf[15], "on", 2))
871 if (!strncasecmp(buf, "prompt_control=", 15)) {
872 if (!strncasecmp(&buf[15], "on", 2))
873 rc_prompt_control = 1;
874 if (!strncasecmp(&buf[15], "user", 4))
875 rc_prompt_control = 3; /* user config */
877 if (!strncasecmp(buf, "username=", 9))
878 strcpy(rc_username, &buf[9]);
880 if (!strncasecmp(buf, "password=", 9))
881 strcpy(rc_password, &buf[9]);
883 if (!strncasecmp(buf, "urlcmd=", 7))
884 strcpy(rc_url_cmd, &buf[7]);
886 if (!strncasecmp(buf, "gotmailcmd=", 11))
887 strcpy(rc_gotmail_cmd, &buf[11]);
889 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
890 if (!strncasecmp(&buf[20], "yes", 3)) {
891 rc_alt_semantics = 1;
894 rc_alt_semantics = 0;
897 if (!strncasecmp(buf, "reply_with_external_editor=", 27)) {
898 if (!strncasecmp(&buf[27], "yes", 3)) {
899 rc_reply_extedit = 1;
902 rc_reply_extedit = 0;
905 if (!strncasecmp(buf, "cmd=", 4)) {
906 strcpy(buf, &buf[4]);
908 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
910 cptr->c_cmdnum = atoi(buf);
911 for (d = strlen(buf); d >= 0; --d)
914 strcpy(buf, &buf[b + 1]);
916 cptr->c_axlevel = atoi(buf);
917 for (d = strlen(buf); d >= 0; --d)
920 strcpy(buf, &buf[b + 1]);
922 for (a = 0; a < 5; ++a)
923 cptr->c_keys[a][0] = 0;
927 buf[strlen(buf) + 1] = 0;
928 while (strlen(buf) > 0) {
930 for (d = strlen(buf); d >= 0; --d)
933 strncpy(cptr->c_keys[a], buf, b);
934 cptr->c_keys[a][b] = 0;
936 strcpy(buf, &buf[b + 1]);
946 lastcmd->next = cptr;
956 * return the key associated with a command
958 char keycmd(char *cmdstr)
962 for (a = 0; a < strlen(cmdstr); ++a)
963 if (cmdstr[a] == '&')
964 return (tolower(cmdstr[a + 1]));
970 * Output the string from a key command without the ampersand
971 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
973 char *cmd_expand(char *strbuf, int mode)
981 for (a = 0; a < strlen(exp); ++a) {
982 if (strbuf[a] == '&') {
985 strcpy(&exp[a], &exp[a + 1]);
989 strcpy(buf, &exp[a + 2]);
995 if (!strncmp(&exp[a], "^r", 2)) {
997 strcpy(&exp[a], room_name);
998 strcat(exp, &buf[a + 2]);
1000 if (!strncmp(&exp[a], "^c", 2)) {
1002 strcpy(&exp[a + 1], &exp[a + 2]);
1012 * Comparison function to determine if entered commands match a
1013 * command loaded from the config file.
1015 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1026 for (a = 0; a < ncomp; ++a) {
1027 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1028 || (cptr->c_axlevel > cmdax))
1036 * This function returns 1 if a given command requires a string input
1038 int requires_string(struct citcmd *cptr, int ncomp)
1043 strcpy(buf, cptr->c_keys[ncomp - 1]);
1044 for (a = 0; a < strlen(buf); ++a) {
1053 * Input a command at the main prompt.
1054 * This function returns an integer command number. If the command prompts
1055 * for a string then it is placed in the supplied buffer.
1057 int getcmd(CtdlIPC *ipc, char *argbuf)
1066 struct citcmd *cptr;
1069 * Starting a new command now, so set sigcaught to 0. This variable
1070 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1071 * been interrupted by a keypress.
1075 /* Switch color support on or off if we're in user mode */
1076 if (rc_ansi_color == 3) {
1077 if (userflags & US_COLOR)
1082 /* if we're running in idiot mode, display a cute little menu */
1083 IFNEXPERT formout(ipc, "mainmenu");
1088 for (a = 0; a < 5; ++a)
1090 /* now the room prompt... */
1091 ok_to_interrupt = 1;
1092 color(BRIGHT_WHITE);
1093 scr_printf("\n%s", room_name);
1095 scr_printf("%c ", room_prompt(room_flags));
1100 ok_to_interrupt = 0;
1102 /* Handle the backspace key, but only if there's something
1103 * to backspace over...
1105 if ((ch == 8) && (cmdpos > 0)) {
1106 back(cmdspaces[cmdpos - 1] + 1);
1110 /* Spacebar invokes "lazy traversal" commands */
1111 if ((ch == 32) && (cmdpos == 0)) {
1112 this_lazy_cmd = next_lazy_cmd;
1113 if (this_lazy_cmd == 13)
1115 if (this_lazy_cmd == 5)
1117 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1118 if (cptr->c_cmdnum == this_lazy_cmd) {
1119 for (a = 0; a < 5; ++a)
1120 if (cptr->c_keys[a][0] != 0)
1121 scr_printf("%s ", cmd_expand(
1122 cptr->c_keys[a], 0));
1124 return (this_lazy_cmd);
1128 return (this_lazy_cmd);
1130 /* Otherwise, process the command */
1131 cmdbuf[cmdpos] = tolower(ch);
1133 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1134 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1136 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1137 cmdspaces[cmdpos] = strlen(
1138 cmd_expand(cptr->c_keys[cmdpos], 0));
1140 if ((cptr->c_keys[cmdpos + 1]) != 0)
1146 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1147 if (cmdmatch(cmdbuf, cptr, 5)) {
1148 /* We've found our command. */
1149 if (requires_string(cptr, cmdpos)) {
1150 getline(argbuf, 32);
1155 /* If this command is one that changes rooms,
1156 * then the next lazy-command (space bar)
1157 * should be "read new" instead of "goto"
1159 if ((cptr->c_cmdnum == 5)
1160 || (cptr->c_cmdnum == 6)
1161 || (cptr->c_cmdnum == 47)
1162 || (cptr->c_cmdnum == 52)
1163 || (cptr->c_cmdnum == 16)
1164 || (cptr->c_cmdnum == 20))
1167 /* If this command is "read new"
1168 * then the next lazy-command (space bar)
1171 if (cptr->c_cmdnum == 13)
1174 return (cptr->c_cmdnum);
1180 pprintf("\rOne of ... \n");
1181 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1182 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1183 for (a = 0; a < 5; ++a) {
1184 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1190 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1192 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1193 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1194 for (a = 0; a < cmdpos; ++a) {
1196 cmd_expand(cptr->c_keys[a], 0));
1211 * set tty modes. commands are:
1213 * 01- set to bbs mode
1214 * 2 - save current settings for later restoral
1215 * 3 - restore saved settings
1217 #ifdef HAVE_TERMIOS_H
1218 void sttybbs(int cmd)
1219 { /* SysV version of sttybbs() */
1220 struct termios live;
1221 static struct termios saved_settings;
1222 static int last_cmd = 0;
1229 if ((cmd == 0) || (cmd == 1)) {
1230 tcgetattr(0, &live);
1231 live.c_iflag = ISTRIP | IXON | IXANY;
1232 live.c_oflag = OPOST | ONLCR;
1233 live.c_lflag = ISIG | NOFLSH;
1235 live.c_cc[VINTR] = 0;
1236 live.c_cc[VQUIT] = 0;
1239 live.c_cc[VMIN] = 0;
1240 live.c_cc[VTIME] = 0;
1243 /* do we even need this stuff anymore? */
1244 /* live.c_line=0; */
1245 live.c_cc[VERASE] = 8;
1246 live.c_cc[VKILL] = 24;
1247 live.c_cc[VEOF] = 1;
1248 live.c_cc[VEOL] = 255;
1249 live.c_cc[VEOL2] = 0;
1250 live.c_cc[VSTART] = 0;
1251 tcsetattr(0, TCSADRAIN, &live);
1254 tcgetattr(0, &saved_settings);
1257 tcsetattr(0, TCSADRAIN, &saved_settings);
1262 void sttybbs(int cmd)
1263 { /* BSD version of sttybbs() */
1265 static struct sgttyb saved_settings;
1266 static int last_cmd = 0;
1273 if ((cmd == 0) || (cmd == 1)) {
1275 live.sg_flags |= CBREAK;
1276 live.sg_flags |= CRMOD;
1277 live.sg_flags |= NL1;
1278 live.sg_flags &= ~ECHO;
1280 live.sg_flags |= NOFLSH;
1284 gtty(0, &saved_settings);
1287 stty(0, &saved_settings);
1294 * display_help() - help file viewer
1296 void display_help(CtdlIPC *ipc, char *name)
1303 * fmout() - Citadel text formatter and paginator
1306 int width, /* screen width to use */
1307 FILE *fpin, /* file to read from, or NULL to format given text */
1308 char *text, /* text to be formatted (when fpin is NULL */
1309 FILE *fpout, /* file to write to, or NULL to write to screen */
1310 char pagin, /* nonzero if we should use the paginator */
1311 int height, /* screen height to use */
1312 int starting_lp,/* starting value for lines_printed, -1 for global */
1313 int subst) /* nonzero if we should use hypertext mode */
1315 char *buffer = NULL; /* The current message */
1316 char *word = NULL; /* What we are about to actually print */
1317 char *e; /* Pointer to position in text */
1318 char old = 0; /* The previous character */
1319 int column = 0; /* Current column */
1320 size_t i; /* Generic counter */
1322 /* Space for a single word, which can be at most screenwidth */
1323 word = (char *)calloc(1, width);
1325 err_printf("Can't alloc memory to print message: %s!\n",
1330 /* Read the entire message body into memory */
1332 buffer = load_message_from_file(fpin);
1334 err_printf("Can't print message: %s!\n",
1343 if (starting_lp >= 0)
1344 lines_printed = starting_lp;
1346 /* Run the message body */
1348 /* Catch characters that shouldn't be there at all */
1353 /* First, are we looking at a newline? */
1356 if (*e == ' ') { /* Paragraph */
1358 fprintf(fpout, "\n");
1362 lines_printed = checkpagin(lines_printed, pagin, height);
1365 } else if (old != ' ') {/* Don't print two spaces */
1367 fprintf(fpout, " ");
1376 /* Are we looking at a nonprintable? */
1377 if ( (*e < 32) || (*e > 126) ) {
1381 /* Or are we looking at a space? */
1384 if (column >= width - 1) {
1385 /* Are we in the rightmost column? */
1387 fprintf(fpout, "\n");
1391 lines_printed = checkpagin(lines_printed, pagin, height);
1394 } else if (!(column == 0 && old == ' ')) {
1395 /* Eat only the first space on a line */
1397 fprintf(fpout, " ");
1403 /* ONLY eat the FIRST space on a line */
1409 /* Read a word, slightly messy */
1412 if (!isprint(e[i]) && !isspace(e[i]))
1419 /* We should never see these, but... slightly messy */
1420 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1423 /* Break up really long words */
1424 /* TODO: auto-hyphenation someday? */
1427 strncpy(word, e, i);
1430 /* Decide where to print the word */
1431 if (column + i >= width) {
1432 /* Wrap to the next line */
1434 fprintf(fpout, "\n");
1438 lines_printed = checkpagin(lines_printed, pagin, height);
1443 /* Print the word */
1445 fprintf(fpout, "%s", word);
1447 scr_printf("%s", word);
1450 e += i; /* Start over with the whitepsace! */
1454 if (fpin) /* We allocated this, remember? */
1457 /* Is this necessary? It makes the output kind of spacey. */
1459 fprintf(fpout, "\n");
1463 lines_printed = checkpagin(lines_printed, pagin, height);
1471 * support ANSI color if defined
1473 void color(int colornum)
1475 static int hold_color;
1476 static int current_color;
1478 if (colornum == COLOR_PUSH) {
1479 hold_color = current_color;
1483 if (colornum == COLOR_POP) {
1488 current_color = colornum;
1490 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1491 if (scr_color(colornum))
1494 /* When switching to dim white, actually output an 'original
1495 * pair' sequence -- this looks better on black-on-white
1496 * terminals. - Changed to ORIGINAL_PAIR as this actually
1497 * wound up looking horrible on black-on-white terminals, not
1498 * to mention transparent terminals.
1500 if (colornum == ORIGINAL_PAIR)
1501 printf("\033[0;39;49m");
1503 printf("\033[%d;3%d;4%dm",
1504 (colornum & 8) ? 1 : 0,
1512 void cls(int colornum)
1515 printf("\033[4%dm\033[2J\033[H\033[0m",
1516 colornum ? colornum : rc_color_use_bg);
1523 * Detect whether ANSI color is available (answerback)
1525 void send_ansi_detect(void)
1527 if (rc_ansi_color == 2) {
1534 void look_for_ansi(void)
1542 if (rc_ansi_color == 0) {
1544 } else if (rc_ansi_color == 1) {
1546 } else if (rc_ansi_color == 2) {
1548 /* otherwise, do the auto-detect */
1553 if ((now - AnsiDetect) < 2)
1562 select(1, &rfds, NULL, NULL, &tv);
1563 if (FD_ISSET(0, &rfds)) {
1564 abuf[strlen(abuf) + 1] = 0;
1565 read(0, &abuf[strlen(abuf)], 1);
1567 } while (FD_ISSET(0, &rfds));
1569 for (a = 0; a < strlen(abuf); ++a) {
1570 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1571 && (abuf[a + 2] == '?')) {
1580 * Display key options (highlight hotkeys inside angle brackets)
1582 void keyopt(char *buf) {
1586 for (i=0; i<strlen(buf); ++i) {
1589 color(BRIGHT_MAGENTA);
1603 * Present a key-menu line choice type of thing
1605 char keymenu(char *menuprompt, char *menustring) {
1611 int display_prompt = 1;
1613 choices = num_tokens(menustring, '|');
1615 if (menuprompt != NULL) do_prompt = 1;
1616 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1619 if (display_prompt) {
1621 scr_printf("%s ", menuprompt);
1624 for (i=0; i<choices; ++i) {
1625 extract(buf, menustring, i);
1635 if ( (do_prompt) && (ch=='?') ) {
1636 scr_printf("\rOne of... ");
1638 for (i=0; i<choices; ++i) {
1639 extract(buf, menustring, i);
1648 for (i=0; i<choices; ++i) {
1649 extract(buf, menustring, i);
1650 for (c=1; c<strlen(buf); ++c) {
1651 if ( (ch == tolower(buf[c]))
1653 && (buf[c+1]=='>') ) {
1654 for (a=0; a<strlen(buf); ++a) {
1655 if ( (a!=(c-1)) && (a!=(c+1))) {