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[20], "yes", 3)) {
886 rc_alt_semantics = 1;
889 rc_alt_semantics = 0;
892 if (!strncasecmp(buf, "cmd=", 4)) {
893 strcpy(buf, &buf[4]);
895 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
897 cptr->c_cmdnum = atoi(buf);
898 for (d = strlen(buf); d >= 0; --d)
901 strcpy(buf, &buf[b + 1]);
903 cptr->c_axlevel = atoi(buf);
904 for (d = strlen(buf); d >= 0; --d)
907 strcpy(buf, &buf[b + 1]);
909 for (a = 0; a < 5; ++a)
910 cptr->c_keys[a][0] = 0;
914 buf[strlen(buf) + 1] = 0;
915 while (strlen(buf) > 0) {
917 for (d = strlen(buf); d >= 0; --d)
920 strncpy(cptr->c_keys[a], buf, b);
921 cptr->c_keys[a][b] = 0;
923 strcpy(buf, &buf[b + 1]);
933 lastcmd->next = cptr;
943 * return the key associated with a command
945 char keycmd(char *cmdstr)
949 for (a = 0; a < strlen(cmdstr); ++a)
950 if (cmdstr[a] == '&')
951 return (tolower(cmdstr[a + 1]));
957 * Output the string from a key command without the ampersand
958 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
960 char *cmd_expand(char *strbuf, int mode)
968 for (a = 0; a < strlen(exp); ++a) {
969 if (strbuf[a] == '&') {
972 strcpy(&exp[a], &exp[a + 1]);
976 strcpy(buf, &exp[a + 2]);
982 if (!strncmp(&exp[a], "^r", 2)) {
984 strcpy(&exp[a], room_name);
985 strcat(exp, &buf[a + 2]);
987 if (!strncmp(&exp[a], "^c", 2)) {
989 strcpy(&exp[a + 1], &exp[a + 2]);
999 * Comparison function to determine if entered commands match a
1000 * command loaded from the config file.
1002 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1013 for (a = 0; a < ncomp; ++a) {
1014 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1015 || (cptr->c_axlevel > cmdax))
1023 * This function returns 1 if a given command requires a string input
1025 int requires_string(struct citcmd *cptr, int ncomp)
1030 strcpy(buf, cptr->c_keys[ncomp - 1]);
1031 for (a = 0; a < strlen(buf); ++a) {
1040 * Input a command at the main prompt.
1041 * This function returns an integer command number. If the command prompts
1042 * for a string then it is placed in the supplied buffer.
1044 int getcmd(CtdlIPC *ipc, char *argbuf)
1053 struct citcmd *cptr;
1056 * Starting a new command now, so set sigcaught to 0. This variable
1057 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1058 * been interrupted by a keypress.
1062 /* Switch color support on or off if we're in user mode */
1063 if (rc_ansi_color == 3) {
1064 if (userflags & US_COLOR)
1069 /* if we're running in idiot mode, display a cute little menu */
1070 IFNEXPERT formout(ipc, "mainmenu");
1075 for (a = 0; a < 5; ++a)
1077 /* now the room prompt... */
1078 ok_to_interrupt = 1;
1079 color(BRIGHT_WHITE);
1080 scr_printf("\n%s", room_name);
1082 scr_printf("%c ", room_prompt(room_flags));
1087 ok_to_interrupt = 0;
1089 /* Handle the backspace key, but only if there's something
1090 * to backspace over...
1092 if ((ch == 8) && (cmdpos > 0)) {
1093 back(cmdspaces[cmdpos - 1] + 1);
1097 /* Spacebar invokes "lazy traversal" commands */
1098 if ((ch == 32) && (cmdpos == 0)) {
1099 this_lazy_cmd = next_lazy_cmd;
1100 if (this_lazy_cmd == 13)
1102 if (this_lazy_cmd == 5)
1104 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1105 if (cptr->c_cmdnum == this_lazy_cmd) {
1106 for (a = 0; a < 5; ++a)
1107 if (cptr->c_keys[a][0] != 0)
1108 scr_printf("%s ", cmd_expand(
1109 cptr->c_keys[a], 0));
1111 return (this_lazy_cmd);
1115 return (this_lazy_cmd);
1117 /* Otherwise, process the command */
1118 cmdbuf[cmdpos] = tolower(ch);
1120 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1121 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1123 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1124 cmdspaces[cmdpos] = strlen(
1125 cmd_expand(cptr->c_keys[cmdpos], 0));
1127 if ((cptr->c_keys[cmdpos + 1]) != 0)
1133 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1134 if (cmdmatch(cmdbuf, cptr, 5)) {
1135 /* We've found our command. */
1136 if (requires_string(cptr, cmdpos)) {
1137 getline(argbuf, 32);
1142 /* If this command is one that changes rooms,
1143 * then the next lazy-command (space bar)
1144 * should be "read new" instead of "goto"
1146 if ((cptr->c_cmdnum == 5)
1147 || (cptr->c_cmdnum == 6)
1148 || (cptr->c_cmdnum == 47)
1149 || (cptr->c_cmdnum == 52)
1150 || (cptr->c_cmdnum == 16)
1151 || (cptr->c_cmdnum == 20))
1154 /* If this command is "read new"
1155 * then the next lazy-command (space bar)
1158 if (cptr->c_cmdnum == 13)
1161 return (cptr->c_cmdnum);
1167 pprintf("\rOne of ... \n");
1168 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1169 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1170 for (a = 0; a < 5; ++a) {
1171 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1177 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1179 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1180 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1181 for (a = 0; a < cmdpos; ++a) {
1183 cmd_expand(cptr->c_keys[a], 0));
1198 * set tty modes. commands are:
1200 * 01- set to bbs mode
1201 * 2 - save current settings for later restoral
1202 * 3 - restore saved settings
1204 #ifdef HAVE_TERMIOS_H
1205 void sttybbs(int cmd)
1206 { /* SysV version of sttybbs() */
1207 struct termios live;
1208 static struct termios saved_settings;
1209 static int last_cmd = 0;
1216 if ((cmd == 0) || (cmd == 1)) {
1217 tcgetattr(0, &live);
1218 live.c_iflag = ISTRIP | IXON | IXANY;
1219 live.c_oflag = OPOST | ONLCR;
1220 live.c_lflag = ISIG | NOFLSH;
1222 live.c_cc[VINTR] = 0;
1223 live.c_cc[VQUIT] = 0;
1226 live.c_cc[VMIN] = 0;
1227 live.c_cc[VTIME] = 0;
1230 /* do we even need this stuff anymore? */
1231 /* live.c_line=0; */
1232 live.c_cc[VERASE] = 8;
1233 live.c_cc[VKILL] = 24;
1234 live.c_cc[VEOF] = 1;
1235 live.c_cc[VEOL] = 255;
1236 live.c_cc[VEOL2] = 0;
1237 live.c_cc[VSTART] = 0;
1238 tcsetattr(0, TCSADRAIN, &live);
1241 tcgetattr(0, &saved_settings);
1244 tcsetattr(0, TCSADRAIN, &saved_settings);
1249 void sttybbs(int cmd)
1250 { /* BSD version of sttybbs() */
1252 static struct sgttyb saved_settings;
1253 static int last_cmd = 0;
1260 if ((cmd == 0) || (cmd == 1)) {
1262 live.sg_flags |= CBREAK;
1263 live.sg_flags |= CRMOD;
1264 live.sg_flags |= NL1;
1265 live.sg_flags &= ~ECHO;
1267 live.sg_flags |= NOFLSH;
1271 gtty(0, &saved_settings);
1274 stty(0, &saved_settings);
1281 * display_help() - help file viewer
1283 void display_help(CtdlIPC *ipc, char *name)
1290 * fmout() - Citadel text formatter and paginator
1293 int width, /* screen width to use */
1294 FILE *fpin, /* file to read from, or NULL to format given text */
1295 char *text, /* text to be formatted (when fpin is NULL */
1296 FILE *fpout, /* file to write to, or NULL to write to screen */
1297 char pagin, /* nonzero if we should use the paginator */
1298 int height, /* screen height to use */
1299 int starting_lp,/* starting value for lines_printed, -1 for global */
1300 int subst) /* nonzero if we should use hypertext mode */
1302 char *buffer = NULL; /* The current message */
1303 char *word = NULL; /* What we are about to actually print */
1304 char *e; /* Pointer to position in text */
1305 char old = 0; /* The previous character */
1306 int column = 0; /* Current column */
1307 size_t i; /* Generic counter */
1309 num_urls = 0; /* Start with a clean slate of embedded URL's */
1311 /* Space for a single word, which can be at most screenwidth */
1312 word = (char *)calloc(1, width);
1314 err_printf("Can't alloc memory to print message: %s!\n",
1319 /* Read the entire message body into memory */
1321 buffer = load_message_from_file(fpin);
1323 err_printf("Can't print message: %s!\n",
1332 if (starting_lp >= 0)
1333 lines_printed = starting_lp;
1335 /* Run the message body */
1337 /* Catch characters that shouldn't be there at all */
1342 /* First, are we looking at a newline? */
1345 if (*e == ' ') { /* Paragraph */
1347 fprintf(fpout, "\n");
1351 lines_printed = checkpagin(lines_printed, pagin, height);
1354 } else if (old != ' ') {/* Don't print two spaces */
1356 fprintf(fpout, " ");
1365 /* Or are we looking at a space? */
1368 if (column >= width - 1) {
1369 /* Are we in the rightmost column? */
1371 fprintf(fpout, "\n");
1375 lines_printed = checkpagin(lines_printed, pagin, height);
1378 } else if (!(column == 0 && old == ' ')) {
1379 /* Eat only the first space on a line */
1381 fprintf(fpout, " ");
1387 /* ONLY eat the FIRST space on a line */
1393 /* Read a word, slightly messy */
1396 if (!isprint(e[i]) && !isspace(e[i]))
1403 /* We should never see these, but... slightly messy */
1404 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1408 * Check for and copy URLs
1409 * We will get the entire URL even if it's longer than the
1410 * screen width, as long as the server didn't break it up
1412 if (!strncasecmp(e, "http://", 7) ||
1413 !strncasecmp(e, "ftp://", 6)) {
1416 strncpy(urls[num_urls], e, i);
1417 urls[num_urls][i] = 0;
1418 for (j = 0; j < strlen(e); j++) {
1421 c = urls[num_urls][j];
1422 if (c == '>' || c == '\"' || c == ')' ||
1423 c == ' ' || c == '\n') {
1424 urls[num_urls][j] = 0;
1431 /* Break up really long words */
1432 /* TODO: auto-hyphenation someday? */
1435 strncpy(word, e, i);
1438 /* Decide where to print the word */
1439 if (column + i >= width) {
1440 /* Wrap to the next line */
1442 fprintf(fpout, "\n");
1446 lines_printed = checkpagin(lines_printed, pagin, height);
1451 /* Print the word */
1453 fprintf(fpout, "%s", word);
1455 scr_printf("%s", word);
1458 e += i; /* Start over with the whitepsace! */
1462 if (fpin) /* We allocated this, remember? */
1465 /* Is this necessary? It makes the output kind of spacey. */
1467 fprintf(fpout, "\n");
1471 lines_printed = checkpagin(lines_printed, pagin, height);
1479 * support ANSI color if defined
1481 void color(int colornum)
1483 static int hold_color;
1484 static int current_color;
1486 if (colornum == COLOR_PUSH) {
1487 hold_color = current_color;
1491 if (colornum == COLOR_POP) {
1496 current_color = colornum;
1498 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1499 if (scr_color(colornum))
1502 /* When switching to dim white, actually output an 'original
1503 * pair' sequence -- this looks better on black-on-white
1504 * terminals. - Changed to ORIGINAL_PAIR as this actually
1505 * wound up looking horrible on black-on-white terminals, not
1506 * to mention transparent terminals.
1508 if (colornum == ORIGINAL_PAIR)
1509 printf("\033[0;39;49m");
1511 printf("\033[%d;3%d;4%dm",
1512 (colornum & 8) ? 1 : 0,
1520 void cls(int colornum)
1523 printf("\033[4%dm\033[2J\033[H\033[0m",
1524 colornum ? colornum : rc_color_use_bg);
1531 * Detect whether ANSI color is available (answerback)
1533 void send_ansi_detect(void)
1535 if (rc_ansi_color == 2) {
1542 void look_for_ansi(void)
1550 if (rc_ansi_color == 0) {
1552 } else if (rc_ansi_color == 1) {
1554 } else if (rc_ansi_color == 2) {
1556 /* otherwise, do the auto-detect */
1561 if ((now - AnsiDetect) < 2)
1570 select(1, &rfds, NULL, NULL, &tv);
1571 if (FD_ISSET(0, &rfds)) {
1572 abuf[strlen(abuf) + 1] = 0;
1573 read(0, &abuf[strlen(abuf)], 1);
1575 } while (FD_ISSET(0, &rfds));
1577 for (a = 0; a < strlen(abuf); ++a) {
1578 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1579 && (abuf[a + 2] == '?')) {
1588 * Display key options (highlight hotkeys inside angle brackets)
1590 void keyopt(char *buf) {
1594 for (i=0; i<strlen(buf); ++i) {
1597 color(BRIGHT_MAGENTA);
1611 * Present a key-menu line choice type of thing
1613 char keymenu(char *menuprompt, char *menustring) {
1619 int display_prompt = 1;
1621 choices = num_tokens(menustring, '|');
1623 if (menuprompt != NULL) do_prompt = 1;
1624 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1627 if (display_prompt) {
1629 scr_printf("%s ", menuprompt);
1632 for (i=0; i<choices; ++i) {
1633 extract(buf, menustring, i);
1643 if ( (do_prompt) && (ch=='?') ) {
1644 scr_printf("\rOne of... ");
1646 for (i=0; i<choices; ++i) {
1647 extract(buf, menustring, i);
1656 for (i=0; i<choices; ++i) {
1657 extract(buf, menustring, i);
1658 for (c=1; c<strlen(buf); ++c) {
1659 if ( (ch == tolower(buf[c]))
1661 && (buf[c+1]=='>') ) {
1662 for (a=0; a<strlen(buf); ++a) {
1663 if ( (a!=(c-1)) && (a!=(c+1))) {