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 return (cptr->c_cmdnum);
1158 pprintf("\rOne of ... \n");
1159 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1160 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1161 for (a = 0; a < 5; ++a) {
1162 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1168 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1170 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1171 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1172 for (a = 0; a < cmdpos; ++a) {
1174 cmd_expand(cptr->c_keys[a], 0));
1189 * set tty modes. commands are:
1191 * 01- set to bbs mode
1192 * 2 - save current settings for later restoral
1193 * 3 - restore saved settings
1195 #ifdef HAVE_TERMIOS_H
1196 void sttybbs(int cmd)
1197 { /* SysV version of sttybbs() */
1198 struct termios live;
1199 static struct termios saved_settings;
1200 static int last_cmd = 0;
1207 if ((cmd == 0) || (cmd == 1)) {
1208 tcgetattr(0, &live);
1209 live.c_iflag = ISTRIP | IXON | IXANY;
1210 live.c_oflag = OPOST | ONLCR;
1211 live.c_lflag = ISIG | NOFLSH;
1213 live.c_cc[VINTR] = 0;
1214 live.c_cc[VQUIT] = 0;
1217 live.c_cc[VMIN] = 0;
1218 live.c_cc[VTIME] = 0;
1221 /* do we even need this stuff anymore? */
1222 /* live.c_line=0; */
1223 live.c_cc[VERASE] = 8;
1224 live.c_cc[VKILL] = 24;
1225 live.c_cc[VEOF] = 1;
1226 live.c_cc[VEOL] = 255;
1227 live.c_cc[VEOL2] = 0;
1228 live.c_cc[VSTART] = 0;
1229 tcsetattr(0, TCSADRAIN, &live);
1232 tcgetattr(0, &saved_settings);
1235 tcsetattr(0, TCSADRAIN, &saved_settings);
1240 void sttybbs(int cmd)
1241 { /* BSD version of sttybbs() */
1243 static struct sgttyb saved_settings;
1244 static int last_cmd = 0;
1251 if ((cmd == 0) || (cmd == 1)) {
1253 live.sg_flags |= CBREAK;
1254 live.sg_flags |= CRMOD;
1255 live.sg_flags |= NL1;
1256 live.sg_flags &= ~ECHO;
1258 live.sg_flags |= NOFLSH;
1262 gtty(0, &saved_settings);
1265 stty(0, &saved_settings);
1272 * display_help() - help file viewer
1274 void display_help(CtdlIPC *ipc, char *name)
1281 * fmout() - Citadel text formatter and paginator
1284 int width, /* screen width to use */
1285 FILE *fpin, /* file to read from, or NULL to format given text */
1286 char *text, /* text to be formatted (when fpin is NULL */
1287 FILE *fpout, /* file to write to, or NULL to write to screen */
1288 char pagin, /* nonzero if we should use the paginator */
1289 int height, /* screen height to use */
1290 int starting_lp,/* starting value for lines_printed, -1 for global */
1291 int subst) /* nonzero if we should use hypertext mode */
1293 char *buffer = NULL; /* The current message */
1294 char *word = NULL; /* What we are about to actually print */
1295 char *e; /* Pointer to position in text */
1296 char old = 0; /* The previous character */
1297 int column = 0; /* Current column */
1298 size_t i; /* Generic counter */
1300 num_urls = 0; /* Start with a clean slate of embedded URL's */
1302 /* Space for a single word, which can be at most screenwidth */
1303 word = (char *)calloc(1, width);
1305 err_printf("Can't alloc memory to print message: %s!\n",
1310 /* Read the entire message body into memory */
1312 buffer = load_message_from_file(fpin);
1314 err_printf("Can't print message: %s!\n",
1323 if (starting_lp >= 0)
1324 lines_printed = starting_lp;
1326 /* Run the message body */
1328 /* Catch characters that shouldn't be there at all */
1333 /* First, are we looking at a newline? */
1336 if (*e == ' ') { /* Paragraph */
1338 fprintf(fpout, "\n");
1342 lines_printed = checkpagin(lines_printed, pagin, height);
1345 } else if (old != ' ') {/* Don't print two spaces */
1347 fprintf(fpout, " ");
1356 /* Or are we looking at a space? */
1359 if (column >= width - 1) {
1360 /* Are we in the rightmost column? */
1362 fprintf(fpout, "\n");
1366 lines_printed = checkpagin(lines_printed, pagin, height);
1369 } else if (!(column == 0 && old == ' ')) {
1370 /* Eat only the first space on a line */
1372 fprintf(fpout, " ");
1378 /* ONLY eat the FIRST space on a line */
1384 /* Read a word, slightly messy */
1387 if (!isprint(e[i]) && !isspace(e[i]))
1394 /* We should never see these, but... slightly messy */
1395 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1399 * Check for and copy URLs
1400 * We will get the entire URL even if it's longer than the
1401 * screen width, as long as the server didn't break it up
1403 if (!strncasecmp(e, "http://", 7) ||
1404 !strncasecmp(e, "ftp://", 6)) {
1407 strncpy(urls[num_urls], e, i);
1408 urls[num_urls][i] = 0;
1409 for (j = 0; j < strlen(e); j++) {
1412 c = urls[num_urls][j];
1413 if (c == '>' || c == '\"' || c == ')' ||
1414 c == ' ' || c == '\n') {
1415 urls[num_urls][j] = 0;
1422 /* Break up really long words */
1423 /* TODO: auto-hyphenation someday? */
1426 strncpy(word, e, i);
1429 /* Decide where to print the word */
1430 if (column + i >= width) {
1431 /* Wrap to the next line */
1433 fprintf(fpout, "\n");
1437 lines_printed = checkpagin(lines_printed, pagin, height);
1442 /* Print the word */
1444 fprintf(fpout, "%s", word);
1446 scr_printf("%s", word);
1449 e += i; /* Start over with the whitepsace! */
1453 if (fpin) /* We allocated this, remember? */
1456 /* Is this necessary? It makes the output kind of spacey. */
1458 fprintf(fpout, "\n");
1462 lines_printed = checkpagin(lines_printed, pagin, height);
1470 * support ANSI color if defined
1472 void color(int colornum)
1474 static int hold_color;
1475 static int current_color;
1477 if (colornum == COLOR_PUSH) {
1478 hold_color = current_color;
1482 if (colornum == COLOR_POP) {
1487 current_color = colornum;
1489 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1490 if (scr_color(colornum))
1493 /* When switching to dim white, actually output an 'original
1494 * pair' sequence -- this looks better on black-on-white
1495 * terminals. - Changed to ORIGINAL_PAIR as this actually
1496 * wound up looking horrible on black-on-white terminals, not
1497 * to mention transparent terminals.
1499 if (colornum == ORIGINAL_PAIR)
1500 printf("\033[0;39;49m");
1502 printf("\033[%d;3%d;4%dm",
1503 (colornum & 8) ? 1 : 0,
1511 void cls(int colornum)
1514 printf("\033[4%dm\033[2J\033[H\033[0m",
1515 colornum ? colornum : rc_color_use_bg);
1522 * Detect whether ANSI color is available (answerback)
1524 void send_ansi_detect(void)
1526 if (rc_ansi_color == 2) {
1533 void look_for_ansi(void)
1541 if (rc_ansi_color == 0) {
1543 } else if (rc_ansi_color == 1) {
1545 } else if (rc_ansi_color == 2) {
1547 /* otherwise, do the auto-detect */
1552 if ((now - AnsiDetect) < 2)
1561 select(1, &rfds, NULL, NULL, &tv);
1562 if (FD_ISSET(0, &rfds)) {
1563 abuf[strlen(abuf) + 1] = 0;
1564 read(0, &abuf[strlen(abuf)], 1);
1566 } while (FD_ISSET(0, &rfds));
1568 for (a = 0; a < strlen(abuf); ++a) {
1569 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1570 && (abuf[a + 2] == '?')) {
1579 * Display key options (highlight hotkeys inside angle brackets)
1581 void keyopt(char *buf) {
1585 for (i=0; i<strlen(buf); ++i) {
1588 color(BRIGHT_MAGENTA);
1602 * Present a key-menu line choice type of thing
1604 char keymenu(char *menuprompt, char *menustring) {
1610 int display_prompt = 1;
1612 choices = num_tokens(menustring, '|');
1614 if (menuprompt != NULL) do_prompt = 1;
1615 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1618 if (display_prompt) {
1620 scr_printf("%s ", menuprompt);
1623 for (i=0; i<choices; ++i) {
1624 extract(buf, menustring, i);
1634 if ( (do_prompt) && (ch=='?') ) {
1635 scr_printf("\rOne of... ");
1637 for (i=0; i<choices; ++i) {
1638 extract(buf, menustring, i);
1647 for (i=0; i<choices; ++i) {
1648 extract(buf, menustring, i);
1649 for (c=1; c<strlen(buf); ++c) {
1650 if ( (ch == tolower(buf[c]))
1652 && (buf[c+1]=='>') ) {
1653 for (a=0; a<strlen(buf); ++a) {
1654 if ( (a!=(c-1)) && (a!=(c+1))) {