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 /* If full keepalives are enabled, send a NOOP to the server and
356 * wait for a response.
358 if (keepalives_enabled == KA_YES) {
359 r = CtdlIPCNoop(ipc_for_signal_handlers);
360 if (express_msgs > 0) {
361 if (ok_to_interrupt == 1) {
362 scr_printf("\r%64s\r", "");
364 scr_printf("%s%c ", room_name,
365 room_prompt(room_flags));
371 /* If half keepalives are enabled, send a QNOP to the server (if the
372 * server supports it) and then do nothing.
374 if ( (keepalives_enabled == KA_HALF)
375 && (serv_info.serv_supports_qnop > 0) ) {
376 CtdlIPC_putline(ipc_for_signal_handlers, "QNOP");
380 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
381 encapsulated interface; in theory there should be no need to touch these
382 globals outside of the async_ka_* functions. */
384 #ifdef THREADED_CLIENT
385 static pthread_t ka_thr_handle;
386 static int ka_thr_active = 0;
387 static int async_ka_enabled = 0;
389 static void *ka_thread(void *arg)
391 really_do_keepalive();
392 pthread_detach(ka_thr_handle);
397 /* start up a thread to handle a keepalive in the background */
398 static void async_ka_exec(void)
400 if (!ka_thr_active) {
402 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
403 perror("pthread_create");
408 #endif /* THREADED_CLIENT */
410 /* I changed this from static to not because I need to call it from
411 screen.c, either that or make something in screen.c not static.
412 Fix it how you like. Why all the staticness? stu */
414 void do_keepalive(void)
419 if ((now - idlet) < ((long) S_KEEPALIVE))
422 /* Do a space-backspace to keep telnet sessions from idling out */
423 scr_printf(" %c", 8);
426 #ifdef THREADED_CLIENT
427 if (async_ka_enabled)
431 really_do_keepalive();
435 /* Now the actual async-keepalve API that we expose to higher levels:
436 async_ka_start() and async_ka_end(). These do nothing when we don't have
437 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
439 /* wait for a background keepalive to complete. this must be done before
440 attempting any further server requests! */
441 void async_ka_end(void)
443 #ifdef THREADED_CLIENT
445 pthread_join(ka_thr_handle, NULL);
451 /* tell do_keepalive() that keepalives are asynchronous. */
452 void async_ka_start(void)
454 #ifdef THREADED_CLIENT
461 { /* get a character from the keyboard, with */
462 int a; /* the watchdog timer in effect if necessary */
472 /* This loop waits for keyboard input. If the keepalive
473 * timer expires, it sends a keepalive to the server if
474 * necessary and then waits again.
477 scr_set_windowsize();
479 scr_set_windowsize();
483 tv.tv_sec = S_KEEPALIVE;
486 select(1, &rfds, NULL, NULL, &tv);
487 } while (!FD_ISSET(0, &rfds));
489 /* At this point, there's input, so fetch it.
490 * (There's a hole in the bucket...)
492 a = scr_getc(SCR_NOBLOCK);
499 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
500 && ((a < 32) || (a > 126)))
503 #ifndef DISABLE_CURSES
504 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
516 { /* Returns 1 for yes, 0 for no */
532 /* Returns 1 for yes, 0 for no, arg is default value */
555 /* Gets a line from the terminal */
556 /* string == Pointer to string buffer */
557 /* lim == Maximum length - if negative, no-show */
558 void getline(char *string, int lim)
572 if ((a == 8 || a == 23) && (strlen(string) == 0))
574 if ((a != 10) && (a != 8) && (strlen(string) == lim))
576 if ((a == 8) && (string[0] != 0)) {
577 string[strlen(string) - 1] = 0;
578 scr_putc(8); scr_putc(32); scr_putc(8);
581 if ((a == 23) && (string[0] != 0)) {
583 string[strlen(string) - 1] = 0;
584 scr_putc(8); scr_putc(32); scr_putc(8);
585 } while (strlen(string) && string[strlen(string) - 1] != ' ');
607 * strprompt() - prompt for a string, print the existing value and
608 * allow the user to press return to keep it...
610 void strprompt(char *prompt, char *str, int len)
617 scr_printf("%s ", prompt);
620 color(BRIGHT_MAGENTA);
623 scr_printf("%s", str);
626 for (i=0; i<strlen(str); ++i) {
643 * boolprompt() - prompt for a yes/no, print the existing value and
644 * allow the user to press return to keep it...
646 int boolprompt(char *prompt, int prev_val)
651 scr_printf("%s ", prompt);
654 color(BRIGHT_MAGENTA);
655 scr_printf("%s", (prev_val ? "Yes" : "No"));
659 r = (yesno_d(prev_val));
665 * intprompt() - like strprompt(), except for an integer
666 * (note that it RETURNS the new value!)
668 int intprompt(char *prompt, int ival, int imin, int imax)
676 snprintf(buf, sizeof buf, "%d", i);
677 strprompt(prompt, buf, 15);
679 for (p=0; p<strlen(buf); ++p) {
680 if ( (!isdigit(buf[p]))
681 && ( (buf[p]!='-') || (p!=0) ) )
685 scr_printf("*** Must be no less than %d.\n", imin);
687 scr_printf("*** Must be no more than %d.\n", imax);
688 } while ((i < imin) || (i > imax));
693 * newprompt() - prompt for a string with no existing value
694 * (clears out string buffer first)
696 void newprompt(char *prompt, char *str, int len)
698 color(BRIGHT_MAGENTA);
699 scr_printf("%s", prompt);
707 { /* returns a lower case value */
716 * parse the citadel.rc file
718 void load_command_set(void)
722 char editor_key[100];
724 struct citcmd *lastcmd = NULL;
730 /* first, set up some defaults for non-required variables */
732 for (i = 0; i < MAX_EDITORS; i++)
733 strcpy(editor_paths[i], "");
734 strcpy(printcmd, "");
735 strcpy(rc_username, "");
736 strcpy(rc_password, "");
739 rc_allow_attachments = 0;
740 rc_remember_passwords = 0;
741 strcpy(rc_exp_cmd, "");
742 rc_display_message_numbers = 0;
743 rc_force_mail_prompts = 0;
746 strcpy(rc_url_cmd, "");
747 strcpy(rc_gotmail_cmd, "");
749 rc_encrypt = RC_DEFAULT;
751 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
752 rc_screen = RC_DEFAULT;
754 rc_alt_semantics = 0;
756 /* now try to open the citadel.rc file */
759 if (getenv("HOME") != NULL) {
760 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
761 ccfile = fopen(buf, "r");
763 if (ccfile == NULL) {
764 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
765 ccfile = fopen(buf, "r");
767 if (ccfile == NULL) {
768 ccfile = fopen("/etc/citadel.rc", "r");
770 if (ccfile == NULL) {
771 ccfile = fopen("./citadel.rc", "r");
773 if (ccfile == NULL) {
774 perror("commands: cannot open citadel.rc");
777 while (fgets(buf, sizeof buf, ccfile) != NULL) {
778 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
779 buf[strlen(buf) - 1] = 0;
781 if (!strncasecmp(buf, "encrypt=", 8)) {
782 if (!strcasecmp(&buf[8], "yes")) {
786 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
791 else if (!strcasecmp(&buf[8], "no")) {
794 else if (!strcasecmp(&buf[8], "default")) {
795 rc_encrypt = RC_DEFAULT;
800 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
801 if (!strncasecmp(buf, "fullscreen=", 11)) {
802 if (!strcasecmp(&buf[11], "yes"))
804 else if (!strcasecmp(&buf[11], "no"))
809 if (!strncasecmp(buf, "editor=", 7))
810 strcpy(editor_paths[0], &buf[7]);
812 for (i = 0; i < MAX_EDITORS; i++)
814 sprintf(editor_key, "editor%d=", i);
815 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
816 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
819 if (!strncasecmp(buf, "printcmd=", 9))
820 strcpy(printcmd, &buf[9]);
822 if (!strncasecmp(buf, "expcmd=", 7))
823 strcpy(rc_exp_cmd, &buf[7]);
825 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
826 have_xterm = (char) atoi(&buf[24]);
828 if (!strncasecmp(buf, "use_floors=", 11)) {
829 if (!strcasecmp(&buf[11], "yes"))
830 rc_floor_mode = RC_YES;
831 if (!strcasecmp(&buf[11], "no"))
832 rc_floor_mode = RC_NO;
833 if (!strcasecmp(&buf[11], "default"))
834 rc_floor_mode = RC_DEFAULT;
836 if (!strncasecmp(buf, "beep=", 5)) {
837 rc_exp_beep = atoi(&buf[5]);
839 if (!strncasecmp(buf, "allow_attachments=", 18)) {
840 rc_allow_attachments = atoi(&buf[18]);
842 if (!strncasecmp(buf, "idle_threshold=", 15)) {
843 rc_idle_threshold = atoi(&buf[15]);
845 if (!strncasecmp(buf, "remember_passwords=", 19)) {
846 rc_remember_passwords = atoi(&buf[19]);
848 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
849 rc_display_message_numbers = atoi(&buf[24]);
851 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
852 rc_force_mail_prompts = atoi(&buf[19]);
854 if (!strncasecmp(buf, "ansi_color=", 11)) {
855 if (!strncasecmp(&buf[11], "on", 2))
857 if (!strncasecmp(&buf[11], "auto", 4))
858 rc_ansi_color = 2; /* autodetect */
859 if (!strncasecmp(&buf[11], "user", 4))
860 rc_ansi_color = 3; /* user config */
862 if (!strncasecmp(buf, "use_background=", 15)) {
863 if (!strncasecmp(&buf[15], "on", 2))
866 if (!strncasecmp(buf, "prompt_control=", 15)) {
867 if (!strncasecmp(&buf[15], "on", 2))
868 rc_prompt_control = 1;
869 if (!strncasecmp(&buf[15], "user", 4))
870 rc_prompt_control = 3; /* user config */
872 if (!strncasecmp(buf, "username=", 9))
873 strcpy(rc_username, &buf[9]);
875 if (!strncasecmp(buf, "password=", 9))
876 strcpy(rc_password, &buf[9]);
878 if (!strncasecmp(buf, "urlcmd=", 7))
879 strcpy(rc_url_cmd, &buf[7]);
881 if (!strncasecmp(buf, "gotmailcmd=", 11))
882 strcpy(rc_gotmail_cmd, &buf[11]);
884 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
885 if (!strncasecmp(&buf[11], "yes", 3))
886 rc_alt_semantics = 1;
887 if (!strncasecmp(&buf[11], "no", 2))
888 rc_alt_semantics = 0;
890 if (!strncasecmp(buf, "cmd=", 4)) {
891 strcpy(buf, &buf[4]);
893 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
895 cptr->c_cmdnum = atoi(buf);
896 for (d = strlen(buf); d >= 0; --d)
899 strcpy(buf, &buf[b + 1]);
901 cptr->c_axlevel = atoi(buf);
902 for (d = strlen(buf); d >= 0; --d)
905 strcpy(buf, &buf[b + 1]);
907 for (a = 0; a < 5; ++a)
908 cptr->c_keys[a][0] = 0;
912 buf[strlen(buf) + 1] = 0;
913 while (strlen(buf) > 0) {
915 for (d = strlen(buf); d >= 0; --d)
918 strncpy(cptr->c_keys[a], buf, b);
919 cptr->c_keys[a][b] = 0;
921 strcpy(buf, &buf[b + 1]);
931 lastcmd->next = cptr;
941 * return the key associated with a command
943 char keycmd(char *cmdstr)
947 for (a = 0; a < strlen(cmdstr); ++a)
948 if (cmdstr[a] == '&')
949 return (tolower(cmdstr[a + 1]));
955 * Output the string from a key command without the ampersand
956 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
958 char *cmd_expand(char *strbuf, int mode)
966 for (a = 0; a < strlen(exp); ++a) {
967 if (strbuf[a] == '&') {
970 strcpy(&exp[a], &exp[a + 1]);
974 strcpy(buf, &exp[a + 2]);
980 if (!strncmp(&exp[a], "^r", 2)) {
982 strcpy(&exp[a], room_name);
983 strcat(exp, &buf[a + 2]);
985 if (!strncmp(&exp[a], "^c", 2)) {
987 strcpy(&exp[a + 1], &exp[a + 2]);
997 * Comparison function to determine if entered commands match a
998 * command loaded from the config file.
1000 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1011 for (a = 0; a < ncomp; ++a) {
1012 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1013 || (cptr->c_axlevel > cmdax))
1021 * This function returns 1 if a given command requires a string input
1023 int requires_string(struct citcmd *cptr, int ncomp)
1028 strcpy(buf, cptr->c_keys[ncomp - 1]);
1029 for (a = 0; a < strlen(buf); ++a) {
1038 * Input a command at the main prompt.
1039 * This function returns an integer command number. If the command prompts
1040 * for a string then it is placed in the supplied buffer.
1042 int getcmd(CtdlIPC *ipc, char *argbuf)
1051 struct citcmd *cptr;
1054 * Starting a new command now, so set sigcaught to 0. This variable
1055 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1056 * been interrupted by a keypress.
1060 /* Switch color support on or off if we're in user mode */
1061 if (rc_ansi_color == 3) {
1062 if (userflags & US_COLOR)
1067 /* if we're running in idiot mode, display a cute little menu */
1068 IFNEXPERT formout(ipc, "mainmenu");
1073 for (a = 0; a < 5; ++a)
1075 /* now the room prompt... */
1076 ok_to_interrupt = 1;
1077 color(BRIGHT_WHITE);
1078 scr_printf("\n%s", room_name);
1080 scr_printf("%c ", room_prompt(room_flags));
1085 ok_to_interrupt = 0;
1087 /* Handle the backspace key, but only if there's something
1088 * to backspace over...
1090 if ((ch == 8) && (cmdpos > 0)) {
1091 back(cmdspaces[cmdpos - 1] + 1);
1095 /* Spacebar invokes "lazy traversal" commands */
1096 if ((ch == 32) && (cmdpos == 0)) {
1097 this_lazy_cmd = next_lazy_cmd;
1098 if (this_lazy_cmd == 13)
1100 if (this_lazy_cmd == 5)
1102 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1103 if (cptr->c_cmdnum == this_lazy_cmd) {
1104 for (a = 0; a < 5; ++a)
1105 if (cptr->c_keys[a][0] != 0)
1106 scr_printf("%s ", cmd_expand(
1107 cptr->c_keys[a], 0));
1109 return (this_lazy_cmd);
1113 return (this_lazy_cmd);
1115 /* Otherwise, process the command */
1116 cmdbuf[cmdpos] = tolower(ch);
1118 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1119 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1121 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1122 cmdspaces[cmdpos] = strlen(
1123 cmd_expand(cptr->c_keys[cmdpos], 0));
1125 if ((cptr->c_keys[cmdpos + 1]) != 0)
1131 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1132 if (cmdmatch(cmdbuf, cptr, 5)) {
1133 /* We've found our command. */
1134 if (requires_string(cptr, cmdpos)) {
1135 getline(argbuf, 32);
1140 /* If this command is one that changes rooms,
1141 * then the next lazy-command (space bar)
1142 * should be "read new" instead of "goto"
1144 if ((cptr->c_cmdnum == 5)
1145 || (cptr->c_cmdnum == 6)
1146 || (cptr->c_cmdnum == 47)
1147 || (cptr->c_cmdnum == 52)
1148 || (cptr->c_cmdnum == 16)
1149 || (cptr->c_cmdnum == 20))
1152 /* If this command is "read new"
1153 * then the next lazy-command (space bar)
1156 if (cptr->c_cmdnum == 13)
1159 return (cptr->c_cmdnum);
1165 pprintf("\rOne of ... \n");
1166 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1167 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1168 for (a = 0; a < 5; ++a) {
1169 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1175 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1177 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1178 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1179 for (a = 0; a < cmdpos; ++a) {
1181 cmd_expand(cptr->c_keys[a], 0));
1196 * set tty modes. commands are:
1198 * 01- set to bbs mode
1199 * 2 - save current settings for later restoral
1200 * 3 - restore saved settings
1202 #ifdef HAVE_TERMIOS_H
1203 void sttybbs(int cmd)
1204 { /* SysV version of sttybbs() */
1205 struct termios live;
1206 static struct termios saved_settings;
1207 static int last_cmd = 0;
1214 if ((cmd == 0) || (cmd == 1)) {
1215 tcgetattr(0, &live);
1216 live.c_iflag = ISTRIP | IXON | IXANY;
1217 live.c_oflag = OPOST | ONLCR;
1218 live.c_lflag = ISIG | NOFLSH;
1220 live.c_cc[VINTR] = 0;
1221 live.c_cc[VQUIT] = 0;
1224 live.c_cc[VMIN] = 0;
1225 live.c_cc[VTIME] = 0;
1228 /* do we even need this stuff anymore? */
1229 /* live.c_line=0; */
1230 live.c_cc[VERASE] = 8;
1231 live.c_cc[VKILL] = 24;
1232 live.c_cc[VEOF] = 1;
1233 live.c_cc[VEOL] = 255;
1234 live.c_cc[VEOL2] = 0;
1235 live.c_cc[VSTART] = 0;
1236 tcsetattr(0, TCSADRAIN, &live);
1239 tcgetattr(0, &saved_settings);
1242 tcsetattr(0, TCSADRAIN, &saved_settings);
1247 void sttybbs(int cmd)
1248 { /* BSD version of sttybbs() */
1250 static struct sgttyb saved_settings;
1251 static int last_cmd = 0;
1258 if ((cmd == 0) || (cmd == 1)) {
1260 live.sg_flags |= CBREAK;
1261 live.sg_flags |= CRMOD;
1262 live.sg_flags |= NL1;
1263 live.sg_flags &= ~ECHO;
1265 live.sg_flags |= NOFLSH;
1269 gtty(0, &saved_settings);
1272 stty(0, &saved_settings);
1279 * display_help() - help file viewer
1281 void display_help(CtdlIPC *ipc, char *name)
1288 * fmout() - Citadel text formatter and paginator
1291 int width, /* screen width to use */
1292 FILE *fpin, /* file to read from, or NULL to format given text */
1293 char *text, /* text to be formatted (when fpin is NULL */
1294 FILE *fpout, /* file to write to, or NULL to write to screen */
1295 char pagin, /* nonzero if we should use the paginator */
1296 int height, /* screen height to use */
1297 int starting_lp,/* starting value for lines_printed, -1 for global */
1298 int subst) /* nonzero if we should use hypertext mode */
1300 char *buffer = NULL; /* The current message */
1301 char *word = NULL; /* What we are about to actually print */
1302 char *e; /* Pointer to position in text */
1303 char old = 0; /* The previous character */
1304 int column = 0; /* Current column */
1305 size_t i; /* Generic counter */
1307 num_urls = 0; /* Start with a clean slate of embedded URL's */
1309 /* Space for a single word, which can be at most screenwidth */
1310 word = (char *)calloc(1, width);
1312 err_printf("Can't alloc memory to print message: %s!\n",
1317 /* Read the entire message body into memory */
1319 buffer = load_message_from_file(fpin);
1321 err_printf("Can't print message: %s!\n",
1330 if (starting_lp >= 0)
1331 lines_printed = starting_lp;
1333 /* Run the message body */
1335 /* Catch characters that shouldn't be there at all */
1340 /* First, are we looking at a newline? */
1343 if (*e == ' ') { /* Paragraph */
1345 fprintf(fpout, "\n");
1349 lines_printed = checkpagin(lines_printed, pagin, height);
1352 } else if (old != ' ') {/* Don't print two spaces */
1354 fprintf(fpout, " ");
1363 /* Or are we looking at a space? */
1366 if (column >= width - 1) {
1367 /* Are we in the rightmost column? */
1369 fprintf(fpout, "\n");
1373 lines_printed = checkpagin(lines_printed, pagin, height);
1376 } else if (!(column == 0 && old == ' ')) {
1377 /* Eat only the first space on a line */
1379 fprintf(fpout, " ");
1385 /* ONLY eat the FIRST space on a line */
1391 /* Read a word, slightly messy */
1394 if (!isprint(e[i]) && !isspace(e[i]))
1401 /* We should never see these, but... slightly messy */
1402 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1406 * Check for and copy URLs
1407 * We will get the entire URL even if it's longer than the
1408 * screen width, as long as the server didn't break it up
1410 if (!strncasecmp(e, "http://", 7) ||
1411 !strncasecmp(e, "ftp://", 6)) {
1414 strncpy(urls[num_urls], e, i);
1415 urls[num_urls][i] = 0;
1416 for (j = 0; j < strlen(e); j++) {
1419 c = urls[num_urls][j];
1420 if (c == '>' || c == '\"' || c == ')' ||
1421 c == ' ' || c == '\n') {
1422 urls[num_urls][j] = 0;
1429 /* Break up really long words */
1430 /* TODO: auto-hyphenation someday? */
1433 strncpy(word, e, i);
1436 /* Decide where to print the word */
1437 if (column + i >= width) {
1438 /* Wrap to the next line */
1440 fprintf(fpout, "\n");
1444 lines_printed = checkpagin(lines_printed, pagin, height);
1449 /* Print the word */
1451 fprintf(fpout, "%s", word);
1453 scr_printf("%s", word);
1456 e += i; /* Start over with the whitepsace! */
1460 if (fpin) /* We allocated this, remember? */
1463 /* Is this necessary? It makes the output kind of spacey. */
1465 fprintf(fpout, "\n");
1469 lines_printed = checkpagin(lines_printed, pagin, height);
1477 * support ANSI color if defined
1479 void color(int colornum)
1481 static int hold_color;
1482 static int current_color;
1484 if (colornum == COLOR_PUSH) {
1485 hold_color = current_color;
1489 if (colornum == COLOR_POP) {
1494 current_color = colornum;
1496 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1497 if (scr_color(colornum))
1500 /* When switching to dim white, actually output an 'original
1501 * pair' sequence -- this looks better on black-on-white
1502 * terminals. - Changed to ORIGINAL_PAIR as this actually
1503 * wound up looking horrible on black-on-white terminals, not
1504 * to mention transparent terminals.
1506 if (colornum == ORIGINAL_PAIR)
1507 printf("\033[0;39;49m");
1509 printf("\033[%d;3%d;4%dm",
1510 (colornum & 8) ? 1 : 0,
1518 void cls(int colornum)
1521 printf("\033[4%dm\033[2J\033[H\033[0m",
1522 colornum ? colornum : rc_color_use_bg);
1529 * Detect whether ANSI color is available (answerback)
1531 void send_ansi_detect(void)
1533 if (rc_ansi_color == 2) {
1540 void look_for_ansi(void)
1548 if (rc_ansi_color == 0) {
1550 } else if (rc_ansi_color == 1) {
1552 } else if (rc_ansi_color == 2) {
1554 /* otherwise, do the auto-detect */
1559 if ((now - AnsiDetect) < 2)
1568 select(1, &rfds, NULL, NULL, &tv);
1569 if (FD_ISSET(0, &rfds)) {
1570 abuf[strlen(abuf) + 1] = 0;
1571 read(0, &abuf[strlen(abuf)], 1);
1573 } while (FD_ISSET(0, &rfds));
1575 for (a = 0; a < strlen(abuf); ++a) {
1576 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1577 && (abuf[a + 2] == '?')) {
1586 * Display key options (highlight hotkeys inside angle brackets)
1588 void keyopt(char *buf) {
1592 for (i=0; i<strlen(buf); ++i) {
1595 color(BRIGHT_MAGENTA);
1609 * Present a key-menu line choice type of thing
1611 char keymenu(char *menuprompt, char *menustring) {
1617 int display_prompt = 1;
1619 choices = num_tokens(menustring, '|');
1621 if (menuprompt != NULL) do_prompt = 1;
1622 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1625 if (display_prompt) {
1627 scr_printf("%s ", menuprompt);
1630 for (i=0; i<choices; ++i) {
1631 extract(buf, menustring, i);
1641 if ( (do_prompt) && (ch=='?') ) {
1642 scr_printf("\rOne of... ");
1644 for (i=0; i<choices; ++i) {
1645 extract(buf, menustring, i);
1654 for (i=0; i<choices; ++i) {
1655 extract(buf, menustring, i);
1656 for (c=1; c<strlen(buf); ++c) {
1657 if ( (ch == tolower(buf[c]))
1659 && (buf[c+1]=='>') ) {
1660 for (a=0; a<strlen(buf); ++a) {
1661 if ( (a!=(c-1)) && (a!=(c+1))) {