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"
56 #include "citadel_dirs.c"
69 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
73 char rc_exp_cmd[1024];
74 int rc_allow_attachments;
75 int rc_display_message_numbers;
76 int rc_force_mail_prompts;
77 int rc_remember_passwords;
80 int rc_prompt_control = 0;
81 time_t rc_idle_threshold = (time_t)900;
83 char rc_gotmail_cmd[SIZ];
86 int next_lazy_cmd = 5;
88 int lines_printed = 0; /* line count for paginator */
89 extern int screenwidth, screenheight;
91 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
93 struct citcmd *cmdlist = NULL;
96 /* these variables are local to this module */
97 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
98 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
99 time_t AnsiDetect; /* when did we send the detect code? */
100 int enable_color = 0; /* nonzero for ANSI color */
106 * If an interesting key has been pressed, return its value, otherwise 0
108 char was_a_key_pressed(void) {
118 retval = select(1, &rfds, NULL, NULL, &tv);
120 /* Careful! Disable keepalives during keyboard polling; we're probably
121 * in the middle of a data transfer from the server, in which case
122 * sending a NOOP would throw the client protocol out of sync.
124 if (FD_ISSET(0, &rfds)) {
125 set_keepalives(KA_NO);
126 the_character = inkey();
127 set_keepalives(KA_YES);
132 return(the_character);
140 * Check to see if we need to pause at the end of a screen.
141 * If we do, we have to switch to half keepalives during the pause because
142 * we are probably in the middle of a server operation and the NOOP command
143 * would confuse everything.
145 int checkpagin(int lp, unsigned int pagin, unsigned int height)
149 if (sigcaught) return(lp);
150 thekey = was_a_key_pressed();
151 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
153 if (thekey == 'n' || thekey == 'N')
155 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
156 if (sigcaught) return(lp);
158 if (!pagin) return(0);
159 if (lp>=(height-1)) {
160 set_keepalives(KA_HALF);
161 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
162 set_keepalives(KA_YES);
172 * pprintf() ... paginated version of printf()
174 void pprintf(const char *format, ...) {
176 static char buf[4096]; /* static for performance, change if needed */
179 /* If sigcaught is nonzero, a keypress has interrupted this and we
180 * should just drain output.
182 if (sigcaught) return;
184 /* Otherwise, start spewing... */
185 va_start(arg_ptr, format);
186 vsnprintf(buf, sizeof(buf), format, arg_ptr);
189 for (i=0; i<strlen(buf); ++i) {
193 lines_printed = checkpagin(lines_printed,
194 (userflags & US_PAGINATOR),
203 * print_instant() - print instant messages if there are any
205 void print_instant(void)
214 char *listing = NULL;
215 int r; /* IPC result code */
217 if (instant_msgs == 0)
223 if (strlen(rc_exp_cmd) == 0) {
228 while (instant_msgs != 0) {
229 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
233 instant_msgs = extract_int(buf, 0);
234 timestamp = extract_long(buf, 1);
235 flags = extract_int(buf, 2);
236 extract_token(sender, buf, 3, '|', sizeof sender);
237 extract_token(node, buf, 4, '|', sizeof node);
238 strcpy(last_paged, sender);
240 localtime_r(×tamp, &stamp);
242 /* If the page is a Logoff Request, honor it. */
248 if (strlen(rc_exp_cmd) > 0) {
249 outpipe = popen(rc_exp_cmd, "w");
250 if (outpipe != NULL) {
251 /* Header derived from flags */
254 "Please log off now, as requested ");
256 fprintf(outpipe, "Broadcast message ");
258 fprintf(outpipe, "Chat request ");
260 fprintf(outpipe, "Message ");
261 /* Timestamp. Can this be improved? */
262 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
263 fprintf(outpipe, "at 12:%02d%cm",
265 stamp.tm_hour ? 'p' : 'a');
266 else if (stamp.tm_hour > 12) /* pm */
267 fprintf(outpipe, "at %d:%02dpm",
271 fprintf(outpipe, "at %d:%02dam",
272 stamp.tm_hour, stamp.tm_min);
273 fprintf(outpipe, " from %s", sender);
274 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
275 fprintf(outpipe, " @%s", node);
276 fprintf(outpipe, ":\n%s\n", listing);
278 if (instant_msgs == 0)
283 /* fall back to built-in instant message display */
287 /* Header derived from flags */
289 scr_printf("Please log off now, as requested ");
291 scr_printf("Broadcast message ");
293 scr_printf("Chat request ");
295 scr_printf("Message ");
297 /* Timestamp. Can this be improved? */
298 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
299 scr_printf("at 12:%02d%cm", stamp.tm_min,
300 stamp.tm_hour ? 'p' : 'a');
301 else if (stamp.tm_hour > 12) /* pm */
302 scr_printf("at %d:%02dpm",
303 stamp.tm_hour - 12, stamp.tm_min);
305 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
308 scr_printf(" from %s", sender);
310 /* Remote node, if any */
311 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
312 scr_printf(" @%s", node);
316 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
319 /* when running in curses mode, the scroll bar in most
320 xterm-style programs becomes useless, so it makes sense to
321 pause after a screenful of pages if the user has been idle
322 for a while. However, this is annoying to some of the users
323 who aren't in curses mode and tend to leave their clients
324 idle. keepalives become disabled, resulting in getting booted
325 when coming back to the idle session. but they probably have
326 a working scrollback in their terminal, so disable it in this
329 if (!is_curses_enabled())
332 scr_printf("\n---\n");
339 void set_keepalives(int s)
341 keepalives_enabled = (char) s;
345 * This loop handles the "keepalive" messages sent to the server when idling.
348 static time_t idlet = 0;
349 static void really_do_keepalive(void) {
350 int r; /* IPC response code */
354 /* This may sometimes get called before we are actually connected
355 * to the server. Don't do anything if we aren't connected. -IO
357 if (!ipc_for_signal_handlers)
360 /* If full keepalives are enabled, send a NOOP to the server and
361 * wait for a response.
363 if (keepalives_enabled == KA_YES) {
364 r = CtdlIPCNoop(ipc_for_signal_handlers);
365 if (instant_msgs > 0) {
366 if (ok_to_interrupt == 1) {
367 scr_printf("\r%64s\r", "");
369 scr_printf("%s%c ", room_name,
370 room_prompt(room_flags));
376 /* If half keepalives are enabled, send a QNOP to the server (if the
377 * server supports it) and then do nothing.
379 if ( (keepalives_enabled == KA_HALF)
380 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
381 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
385 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
386 encapsulated interface; in theory there should be no need to touch these
387 globals outside of the async_ka_* functions. */
389 #ifdef THREADED_CLIENT
390 static pthread_t ka_thr_handle;
391 static int ka_thr_active = 0;
392 static int async_ka_enabled = 0;
394 static void *ka_thread(void *arg)
396 really_do_keepalive();
397 pthread_detach(ka_thr_handle);
402 /* start up a thread to handle a keepalive in the background */
403 static void async_ka_exec(void)
405 if (!ka_thr_active) {
407 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
408 perror("pthread_create");
413 #endif /* THREADED_CLIENT */
415 /* I changed this from static to not because I need to call it from
416 screen.c, either that or make something in screen.c not static.
417 Fix it how you like. Why all the staticness? stu */
419 void do_keepalive(void)
424 if ((now - idlet) < ((long) S_KEEPALIVE))
427 /* Do a space-backspace to keep telnet sessions from idling out */
428 scr_printf(" %c", 8);
431 #ifdef THREADED_CLIENT
432 if (async_ka_enabled)
436 really_do_keepalive();
440 /* Now the actual async-keepalve API that we expose to higher levels:
441 async_ka_start() and async_ka_end(). These do nothing when we don't have
442 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
444 /* wait for a background keepalive to complete. this must be done before
445 attempting any further server requests! */
446 void async_ka_end(void)
448 #ifdef THREADED_CLIENT
450 pthread_join(ka_thr_handle, NULL);
456 /* tell do_keepalive() that keepalives are asynchronous. */
457 void async_ka_start(void)
459 #ifdef THREADED_CLIENT
466 { /* get a character from the keyboard, with */
467 int a; /* the watchdog timer in effect if necessary */
477 /* This loop waits for keyboard input. If the keepalive
478 * timer expires, it sends a keepalive to the server if
479 * necessary and then waits again.
482 scr_set_windowsize(ipc_for_signal_handlers);
484 scr_set_windowsize(ipc_for_signal_handlers);
488 tv.tv_sec = S_KEEPALIVE;
491 select(1, &rfds, NULL, NULL, &tv);
492 } while (!FD_ISSET(0, &rfds));
494 /* At this point, there's input, so fetch it.
495 * (There's a hole in the bucket...)
497 a = scr_getc(SCR_BLOCK);
504 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
505 && ((a < 32) || (a > 126)))
508 #ifndef DISABLE_CURSES
509 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
522 { /* Returns 1 for yes, 0 for no */
538 /* Returns 1 for yes, 0 for no, arg is default value */
561 /* Gets a line from the terminal */
562 /* string == Pointer to string buffer */
563 /* lim == Maximum length - if negative, no-show */
564 void getline(char *string, int lim)
578 if ((a == 8 || a == 23) && (strlen(string) == 0))
580 if ((a != 10) && (a != 8) && (strlen(string) == lim))
582 if ((a == 8) && (string[0] != 0)) {
583 string[strlen(string) - 1] = 0;
584 scr_putc(8); scr_putc(32); scr_putc(8);
587 if ((a == 23) && (string[0] != 0)) {
589 string[strlen(string) - 1] = 0;
590 scr_putc(8); scr_putc(32); scr_putc(8);
591 } while (strlen(string) && string[strlen(string) - 1] != ' ');
613 * strprompt() - prompt for a string, print the existing value and
614 * allow the user to press return to keep it...
616 void strprompt(char *prompt, char *str, int len)
623 scr_printf("%s ", prompt);
626 color(BRIGHT_MAGENTA);
629 scr_printf("%s", str);
632 for (i=0; i<strlen(str); ++i) {
649 * boolprompt() - prompt for a yes/no, print the existing value and
650 * allow the user to press return to keep it...
652 int boolprompt(char *prompt, int prev_val)
657 scr_printf("%s ", prompt);
660 color(BRIGHT_MAGENTA);
661 scr_printf("%s", (prev_val ? "Yes" : "No"));
665 r = (yesno_d(prev_val));
671 * intprompt() - like strprompt(), except for an integer
672 * (note that it RETURNS the new value!)
674 int intprompt(char *prompt, int ival, int imin, int imax)
682 snprintf(buf, sizeof buf, "%d", i);
683 strprompt(prompt, buf, 15);
685 for (p=0; p<strlen(buf); ++p) {
686 if ( (!isdigit(buf[p]))
687 && ( (buf[p]!='-') || (p!=0) ) )
691 scr_printf("*** Must be no less than %d.\n", imin);
693 scr_printf("*** Must be no more than %d.\n", imax);
694 } while ((i < imin) || (i > imax));
699 * newprompt() - prompt for a string with no existing value
700 * (clears out string buffer first)
702 void newprompt(char *prompt, char *str, int len)
704 color(BRIGHT_MAGENTA);
705 scr_printf("%s", prompt);
713 { /* returns a lower case value */
722 * parse the citadel.rc file
724 void load_command_set(void)
728 char editor_key[100];
730 struct citcmd *lastcmd = NULL;
736 /* first, set up some defaults for non-required variables */
738 for (i = 0; i < MAX_EDITORS; i++)
739 strcpy(editor_paths[i], "");
740 strcpy(printcmd, "");
741 strcpy(imagecmd, "");
742 strcpy(rc_username, "");
743 strcpy(rc_password, "");
746 rc_allow_attachments = 0;
747 rc_remember_passwords = 0;
748 strcpy(rc_exp_cmd, "");
749 rc_display_message_numbers = 0;
750 rc_force_mail_prompts = 0;
753 strcpy(rc_url_cmd, "");
754 strcpy(rc_gotmail_cmd, "");
756 rc_encrypt = RC_DEFAULT;
758 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
759 rc_screen = RC_DEFAULT;
761 rc_alt_semantics = 0;
763 /* now try to open the citadel.rc file */
766 if (getenv("HOME") != NULL) {
767 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
768 ccfile = fopen(buf, "r");
770 if (ccfile == NULL) {
771 ccfile = fopen(file_citadel_rc, "r");
773 if (ccfile == NULL) {
774 ccfile = fopen("/etc/citadel.rc", "r");
776 if (ccfile == NULL) {
777 ccfile = fopen("./citadel.rc", "r");
779 if (ccfile == NULL) {
780 perror("commands: cannot open citadel.rc");
783 while (fgets(buf, sizeof buf, ccfile) != NULL) {
784 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
785 buf[strlen(buf) - 1] = 0;
787 if (!strncasecmp(buf, "encrypt=", 8)) {
788 if (!strcasecmp(&buf[8], "yes")) {
792 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
797 else if (!strcasecmp(&buf[8], "no")) {
800 else if (!strcasecmp(&buf[8], "default")) {
801 rc_encrypt = RC_DEFAULT;
806 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
807 if (!strncasecmp(buf, "fullscreen=", 11)) {
808 if (!strcasecmp(&buf[11], "yes"))
810 else if (!strcasecmp(&buf[11], "no"))
815 if (!strncasecmp(buf, "editor=", 7))
816 strcpy(editor_paths[0], &buf[7]);
818 for (i = 0; i < MAX_EDITORS; i++)
820 sprintf(editor_key, "editor%d=", i);
821 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
822 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
825 if (!strncasecmp(buf, "printcmd=", 9))
826 strcpy(printcmd, &buf[9]);
828 if (!strncasecmp(buf, "imagecmd=", 9))
829 strcpy(imagecmd, &buf[9]);
831 if (!strncasecmp(buf, "expcmd=", 7))
832 strcpy(rc_exp_cmd, &buf[7]);
834 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
835 have_xterm = (char) atoi(&buf[24]);
837 if (!strncasecmp(buf, "use_floors=", 11)) {
838 if (!strcasecmp(&buf[11], "yes"))
839 rc_floor_mode = RC_YES;
840 if (!strcasecmp(&buf[11], "no"))
841 rc_floor_mode = RC_NO;
842 if (!strcasecmp(&buf[11], "default"))
843 rc_floor_mode = RC_DEFAULT;
845 if (!strncasecmp(buf, "beep=", 5)) {
846 rc_exp_beep = atoi(&buf[5]);
848 if (!strncasecmp(buf, "allow_attachments=", 18)) {
849 rc_allow_attachments = atoi(&buf[18]);
851 if (!strncasecmp(buf, "idle_threshold=", 15)) {
852 rc_idle_threshold = atol(&buf[15]);
854 if (!strncasecmp(buf, "remember_passwords=", 19)) {
855 rc_remember_passwords = atoi(&buf[19]);
857 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
858 rc_display_message_numbers = atoi(&buf[24]);
860 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
861 rc_force_mail_prompts = atoi(&buf[19]);
863 if (!strncasecmp(buf, "ansi_color=", 11)) {
864 if (!strncasecmp(&buf[11], "on", 2))
866 if (!strncasecmp(&buf[11], "auto", 4))
867 rc_ansi_color = 2; /* autodetect */
868 if (!strncasecmp(&buf[11], "user", 4))
869 rc_ansi_color = 3; /* user config */
871 if (!strncasecmp(buf, "use_background=", 15)) {
872 if (!strncasecmp(&buf[15], "on", 2))
875 if (!strncasecmp(buf, "prompt_control=", 15)) {
876 if (!strncasecmp(&buf[15], "on", 2))
877 rc_prompt_control = 1;
878 if (!strncasecmp(&buf[15], "user", 4))
879 rc_prompt_control = 3; /* user config */
881 if (!strncasecmp(buf, "username=", 9))
882 strcpy(rc_username, &buf[9]);
884 if (!strncasecmp(buf, "password=", 9))
885 strcpy(rc_password, &buf[9]);
887 if (!strncasecmp(buf, "urlcmd=", 7))
888 strcpy(rc_url_cmd, &buf[7]);
890 if (!strncasecmp(buf, "gotmailcmd=", 11))
891 strcpy(rc_gotmail_cmd, &buf[11]);
893 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
894 if (!strncasecmp(&buf[20], "yes", 3)) {
895 rc_alt_semantics = 1;
898 rc_alt_semantics = 0;
902 if (!strncasecmp(buf, "cmd=", 4)) {
903 strcpy(buf, &buf[4]);
905 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
907 cptr->c_cmdnum = atoi(buf);
908 for (d = strlen(buf); d >= 0; --d)
911 strcpy(buf, &buf[b + 1]);
913 cptr->c_axlevel = atoi(buf);
914 for (d = strlen(buf); d >= 0; --d)
917 strcpy(buf, &buf[b + 1]);
919 for (a = 0; a < 5; ++a)
920 cptr->c_keys[a][0] = 0;
924 buf[strlen(buf) + 1] = 0;
925 while (strlen(buf) > 0) {
927 for (d = strlen(buf); d >= 0; --d)
930 strncpy(cptr->c_keys[a], buf, b);
931 cptr->c_keys[a][b] = 0;
933 strcpy(buf, &buf[b + 1]);
943 lastcmd->next = cptr;
953 * return the key associated with a command
955 char keycmd(char *cmdstr)
959 for (a = 0; a < strlen(cmdstr); ++a)
960 if (cmdstr[a] == '&')
961 return (tolower(cmdstr[a + 1]));
967 * Output the string from a key command without the ampersand
968 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
970 char *cmd_expand(char *strbuf, int mode)
978 for (a = 0; a < strlen(exp); ++a) {
979 if (strbuf[a] == '&') {
982 strcpy(&exp[a], &exp[a + 1]);
986 strcpy(buf, &exp[a + 2]);
992 if (!strncmp(&exp[a], "^r", 2)) {
994 strcpy(&exp[a], room_name);
995 strcat(exp, &buf[a + 2]);
997 if (!strncmp(&exp[a], "^c", 2)) {
999 strcpy(&exp[a + 1], &exp[a + 2]);
1009 * Comparison function to determine if entered commands match a
1010 * command loaded from the config file.
1012 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1023 for (a = 0; a < ncomp; ++a) {
1024 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1025 || (cptr->c_axlevel > cmdax))
1033 * This function returns 1 if a given command requires a string input
1035 int requires_string(struct citcmd *cptr, int ncomp)
1040 strcpy(buf, cptr->c_keys[ncomp - 1]);
1041 for (a = 0; a < strlen(buf); ++a) {
1050 * Input a command at the main prompt.
1051 * This function returns an integer command number. If the command prompts
1052 * for a string then it is placed in the supplied buffer.
1054 int getcmd(CtdlIPC *ipc, char *argbuf)
1063 struct citcmd *cptr;
1066 * Starting a new command now, so set sigcaught to 0. This variable
1067 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1068 * been interrupted by a keypress.
1072 /* Switch color support on or off if we're in user mode */
1073 if (rc_ansi_color == 3) {
1074 if (userflags & US_COLOR)
1079 /* if we're running in idiot mode, display a cute little menu */
1080 IFNEXPERT formout(ipc, "mainmenu");
1085 for (a = 0; a < 5; ++a)
1087 /* now the room prompt... */
1088 ok_to_interrupt = 1;
1089 color(BRIGHT_WHITE);
1090 scr_printf("\n%s", room_name);
1092 scr_printf("%c ", room_prompt(room_flags));
1097 ok_to_interrupt = 0;
1099 /* Handle the backspace key, but only if there's something
1100 * to backspace over...
1102 if ((ch == 8) && (cmdpos > 0)) {
1103 back(cmdspaces[cmdpos - 1] + 1);
1107 /* Spacebar invokes "lazy traversal" commands */
1108 if ((ch == 32) && (cmdpos == 0)) {
1109 this_lazy_cmd = next_lazy_cmd;
1110 if (this_lazy_cmd == 13)
1112 if (this_lazy_cmd == 5)
1114 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1115 if (cptr->c_cmdnum == this_lazy_cmd) {
1116 for (a = 0; a < 5; ++a)
1117 if (cptr->c_keys[a][0] != 0)
1118 scr_printf("%s ", cmd_expand(
1119 cptr->c_keys[a], 0));
1121 return (this_lazy_cmd);
1125 return (this_lazy_cmd);
1127 /* Otherwise, process the command */
1128 cmdbuf[cmdpos] = tolower(ch);
1130 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1131 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1133 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1134 cmdspaces[cmdpos] = strlen(
1135 cmd_expand(cptr->c_keys[cmdpos], 0));
1137 if ((cptr->c_keys[cmdpos + 1]) != 0)
1143 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1144 if (cmdmatch(cmdbuf, cptr, 5)) {
1145 /* We've found our command. */
1146 if (requires_string(cptr, cmdpos)) {
1147 getline(argbuf, 32);
1152 /* If this command is one that changes rooms,
1153 * then the next lazy-command (space bar)
1154 * should be "read new" instead of "goto"
1156 if ((cptr->c_cmdnum == 5)
1157 || (cptr->c_cmdnum == 6)
1158 || (cptr->c_cmdnum == 47)
1159 || (cptr->c_cmdnum == 52)
1160 || (cptr->c_cmdnum == 16)
1161 || (cptr->c_cmdnum == 20))
1164 /* If this command is "read new"
1165 * then the next lazy-command (space bar)
1168 if (cptr->c_cmdnum == 13)
1171 return (cptr->c_cmdnum);
1177 pprintf("\rOne of ... \n");
1178 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1179 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1180 for (a = 0; a < 5; ++a) {
1181 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1187 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1189 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1190 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1191 for (a = 0; a < cmdpos; ++a) {
1193 cmd_expand(cptr->c_keys[a], 0));
1208 * set tty modes. commands are:
1210 * 01- set to Citadel mode
1211 * 2 - save current settings for later restoral
1212 * 3 - restore saved settings
1214 #ifdef HAVE_TERMIOS_H
1215 void stty_ctdl(int cmd)
1216 { /* SysV version of stty_ctdl() */
1217 struct termios live;
1218 static struct termios saved_settings;
1219 static int last_cmd = 0;
1226 if ((cmd == 0) || (cmd == 1)) {
1227 tcgetattr(0, &live);
1228 live.c_iflag = ISTRIP | IXON | IXANY;
1229 live.c_oflag = OPOST | ONLCR;
1230 live.c_lflag = ISIG | NOFLSH;
1232 live.c_cc[VINTR] = 0;
1233 live.c_cc[VQUIT] = 0;
1236 live.c_cc[VMIN] = 0;
1237 live.c_cc[VTIME] = 0;
1240 /* do we even need this stuff anymore? */
1241 /* live.c_line=0; */
1242 live.c_cc[VERASE] = 8;
1243 live.c_cc[VKILL] = 24;
1244 live.c_cc[VEOF] = 1;
1245 live.c_cc[VEOL] = 255;
1246 live.c_cc[VEOL2] = 0;
1247 live.c_cc[VSTART] = 0;
1248 tcsetattr(0, TCSADRAIN, &live);
1251 tcgetattr(0, &saved_settings);
1254 tcsetattr(0, TCSADRAIN, &saved_settings);
1259 void stty_ctdl(int cmd)
1260 { /* BSD version of stty_ctdl() */
1262 static struct sgttyb saved_settings;
1263 static int last_cmd = 0;
1270 if ((cmd == 0) || (cmd == 1)) {
1272 live.sg_flags |= CBREAK;
1273 live.sg_flags |= CRMOD;
1274 live.sg_flags |= NL1;
1275 live.sg_flags &= ~ECHO;
1277 live.sg_flags |= NOFLSH;
1281 gtty(0, &saved_settings);
1284 stty(0, &saved_settings);
1291 * display_help() - help file viewer
1293 void display_help(CtdlIPC *ipc, char *name)
1300 * fmout() - Citadel text formatter and paginator
1303 int width, /* screen width to use */
1304 FILE *fpin, /* file to read from, or NULL to format given text */
1305 char *text, /* text to be formatted (when fpin is NULL */
1306 FILE *fpout, /* file to write to, or NULL to write to screen */
1307 char pagin, /* nonzero if we should use the paginator */
1308 int height, /* screen height to use */
1309 int starting_lp,/* starting value for lines_printed, -1 for global */
1310 int subst) /* nonzero if we should use hypertext mode */
1312 char *buffer = NULL; /* The current message */
1313 char *word = NULL; /* What we are about to actually print */
1314 char *e; /* Pointer to position in text */
1315 char old = 0; /* The previous character */
1316 int column = 0; /* Current column */
1317 size_t i; /* Generic counter */
1319 /* Space for a single word, which can be at most screenwidth */
1320 word = (char *)calloc(1, width);
1322 err_printf("Can't alloc memory to print message: %s!\n",
1327 /* Read the entire message body into memory */
1329 buffer = load_message_from_file(fpin);
1331 err_printf("Can't print message: %s!\n",
1340 if (starting_lp >= 0)
1341 lines_printed = starting_lp;
1343 /* Run the message body */
1345 /* Catch characters that shouldn't be there at all */
1350 /* First, are we looking at a newline? */
1353 if (*e == ' ') { /* Paragraph */
1355 fprintf(fpout, "\n");
1359 lines_printed = checkpagin(lines_printed, pagin, height);
1362 } else if (old != ' ') {/* Don't print two spaces */
1364 fprintf(fpout, " ");
1373 /* Are we looking at a nonprintable? */
1374 if ( (*e < 32) || (*e > 126) ) {
1378 /* Or are we looking at a space? */
1381 if (column >= width - 1) {
1382 /* Are we in the rightmost column? */
1384 fprintf(fpout, "\n");
1388 lines_printed = checkpagin(lines_printed, pagin, height);
1391 } else if (!(column == 0 && old == ' ')) {
1392 /* Eat only the first space on a line */
1394 fprintf(fpout, " ");
1400 /* ONLY eat the FIRST space on a line */
1406 /* Read a word, slightly messy */
1409 if (!isprint(e[i]) && !isspace(e[i]))
1416 /* We should never see these, but... slightly messy */
1417 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1420 /* Break up really long words */
1421 /* TODO: auto-hyphenation someday? */
1424 strncpy(word, e, i);
1427 /* Decide where to print the word */
1428 if (column + i >= width) {
1429 /* Wrap to the next line */
1431 fprintf(fpout, "\n");
1435 lines_printed = checkpagin(lines_printed, pagin, height);
1440 /* Print the word */
1442 fprintf(fpout, "%s", word);
1444 scr_printf("%s", word);
1447 e += i; /* Start over with the whitepsace! */
1451 if (fpin) /* We allocated this, remember? */
1454 /* Is this necessary? It makes the output kind of spacey. */
1456 fprintf(fpout, "\n");
1460 lines_printed = checkpagin(lines_printed, pagin, height);
1468 * support ANSI color if defined
1470 void color(int colornum)
1472 static int hold_color;
1473 static int current_color;
1475 if (colornum == COLOR_PUSH) {
1476 hold_color = current_color;
1480 if (colornum == COLOR_POP) {
1485 current_color = colornum;
1487 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1488 if (scr_color(colornum))
1491 /* When switching to dim white, actually output an 'original
1492 * pair' sequence -- this looks better on black-on-white
1493 * terminals. - Changed to ORIGINAL_PAIR as this actually
1494 * wound up looking horrible on black-on-white terminals, not
1495 * to mention transparent terminals.
1497 if (colornum == ORIGINAL_PAIR)
1498 printf("\033[0;39;49m");
1500 printf("\033[%d;3%d;4%dm",
1501 (colornum & 8) ? 1 : 0,
1509 void cls(int colornum)
1512 printf("\033[4%dm\033[2J\033[H\033[0m",
1513 colornum ? colornum : rc_color_use_bg);
1520 * Detect whether ANSI color is available (answerback)
1522 void send_ansi_detect(void)
1524 if (rc_ansi_color == 2) {
1531 void look_for_ansi(void)
1539 if (rc_ansi_color == 0) {
1541 } else if (rc_ansi_color == 1) {
1543 } else if (rc_ansi_color == 2) {
1545 /* otherwise, do the auto-detect */
1550 if ((now - AnsiDetect) < 2)
1559 select(1, &rfds, NULL, NULL, &tv);
1560 if (FD_ISSET(0, &rfds)) {
1561 abuf[strlen(abuf) + 1] = 0;
1562 read(0, &abuf[strlen(abuf)], 1);
1564 } while (FD_ISSET(0, &rfds));
1566 for (a = 0; a < strlen(abuf); ++a) {
1567 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1568 && (abuf[a + 2] == '?')) {
1577 * Display key options (highlight hotkeys inside angle brackets)
1579 void keyopt(char *buf) {
1583 for (i=0; i<strlen(buf); ++i) {
1586 color(BRIGHT_MAGENTA);
1600 * Present a key-menu line choice type of thing
1602 char keymenu(char *menuprompt, char *menustring) {
1608 int display_prompt = 1;
1610 choices = num_tokens(menustring, '|');
1612 if (menuprompt != NULL) do_prompt = 1;
1613 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1616 if (display_prompt) {
1618 scr_printf("%s ", menuprompt);
1621 for (i=0; i<choices; ++i) {
1622 extract_token(buf, menustring, i, '|', sizeof buf);
1632 if ( (do_prompt) && (ch=='?') ) {
1633 scr_printf("\rOne of... ");
1635 for (i=0; i<choices; ++i) {
1636 extract_token(buf, menustring, i, '|', sizeof buf);
1645 for (i=0; i<choices; ++i) {
1646 extract_token(buf, menustring, i, '|', sizeof buf);
1647 for (c=1; c<strlen(buf); ++c) {
1648 if ( (ch == tolower(buf[c]))
1650 && (buf[c+1]=='>') ) {
1651 for (a=0; a<strlen(buf); ++a) {
1652 if ( (a!=(c-1)) && (a!=(c+1))) {