4 * This file contains functions which implement parts of the
5 * text-mode user interface.
16 #include <sys/types.h>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
35 #ifdef HAVE_SYS_SELECT_H
36 #include <sys/select.h>
39 #ifdef THREADED_CLIENT
47 #include "citadel_ipc.h"
50 #include "citadel_decls.h"
52 #include "routines2.h"
55 #include "client_chat.h"
68 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
72 char rc_exp_cmd[1024];
73 int rc_allow_attachments;
74 int rc_display_message_numbers;
75 int rc_force_mail_prompts;
76 int rc_remember_passwords;
79 int rc_prompt_control = 0;
80 time_t rc_idle_threshold = (time_t)900;
82 char rc_gotmail_cmd[SIZ];
85 int next_lazy_cmd = 5;
87 int lines_printed = 0; /* line count for paginator */
88 extern int screenwidth, screenheight;
90 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
92 struct citcmd *cmdlist = NULL;
95 /* these variables are local to this module */
96 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
97 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
98 time_t AnsiDetect; /* when did we send the detect code? */
99 int enable_color = 0; /* nonzero for ANSI color */
105 * If an interesting key has been pressed, return its value, otherwise 0
107 char was_a_key_pressed(void) {
117 retval = select(1, &rfds, NULL, NULL, &tv);
119 /* Careful! Disable keepalives during keyboard polling; we're probably
120 * in the middle of a data transfer from the server, in which case
121 * sending a NOOP would throw the client protocol out of sync.
123 if (FD_ISSET(0, &rfds)) {
124 set_keepalives(KA_NO);
125 the_character = inkey();
126 set_keepalives(KA_YES);
131 return(the_character);
139 * Check to see if we need to pause at the end of a screen.
140 * If we do, we have to switch to half keepalives during the pause because
141 * we are probably in the middle of a server operation and the NOOP command
142 * would confuse everything.
144 int checkpagin(int lp, unsigned int pagin, unsigned int height)
148 if (sigcaught) return(lp);
149 thekey = was_a_key_pressed();
150 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
152 if (thekey == 'n' || thekey == 'N')
154 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
155 if (sigcaught) return(lp);
157 if (!pagin) return(0);
158 if (lp>=(height-1)) {
159 set_keepalives(KA_HALF);
160 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
161 set_keepalives(KA_YES);
171 * pprintf() ... paginated version of printf()
173 void pprintf(const char *format, ...) {
175 static char buf[4096]; /* static for performance, change if needed */
178 /* If sigcaught is nonzero, a keypress has interrupted this and we
179 * should just drain output.
181 if (sigcaught) return;
183 /* Otherwise, start spewing... */
184 va_start(arg_ptr, format);
185 vsnprintf(buf, sizeof(buf), format, arg_ptr);
188 for (i=0; i<strlen(buf); ++i) {
192 lines_printed = checkpagin(lines_printed,
193 (userflags & US_PAGINATOR),
202 * print_instant() - print instant messages if there are any
204 void print_instant(void)
213 char *listing = NULL;
214 int r; /* IPC result code */
216 if (instant_msgs == 0)
222 if (strlen(rc_exp_cmd) == 0) {
227 while (instant_msgs != 0) {
228 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
232 instant_msgs = extract_int(buf, 0);
233 timestamp = extract_long(buf, 1);
234 flags = extract_int(buf, 2);
235 extract_token(sender, buf, 3, '|', sizeof sender);
236 extract_token(node, buf, 4, '|', sizeof node);
237 strcpy(last_paged, sender);
239 localtime_r(×tamp, &stamp);
241 /* If the page is a Logoff Request, honor it. */
247 if (strlen(rc_exp_cmd) > 0) {
248 outpipe = popen(rc_exp_cmd, "w");
249 if (outpipe != NULL) {
250 /* Header derived from flags */
253 "Please log off now, as requested ");
255 fprintf(outpipe, "Broadcast message ");
257 fprintf(outpipe, "Chat request ");
259 fprintf(outpipe, "Message ");
260 /* Timestamp. Can this be improved? */
261 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
262 fprintf(outpipe, "at 12:%02d%cm",
264 stamp.tm_hour ? 'p' : 'a');
265 else if (stamp.tm_hour > 12) /* pm */
266 fprintf(outpipe, "at %d:%02dpm",
270 fprintf(outpipe, "at %d:%02dam",
271 stamp.tm_hour, stamp.tm_min);
272 fprintf(outpipe, " from %s", sender);
273 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
274 fprintf(outpipe, " @%s", node);
275 fprintf(outpipe, ":\n%s\n", listing);
277 if (instant_msgs == 0)
282 /* fall back to built-in instant message display */
286 /* Header derived from flags */
288 scr_printf("Please log off now, as requested ");
290 scr_printf("Broadcast message ");
292 scr_printf("Chat request ");
294 scr_printf("Message ");
296 /* Timestamp. Can this be improved? */
297 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
298 scr_printf("at 12:%02d%cm", stamp.tm_min,
299 stamp.tm_hour ? 'p' : 'a');
300 else if (stamp.tm_hour > 12) /* pm */
301 scr_printf("at %d:%02dpm",
302 stamp.tm_hour - 12, stamp.tm_min);
304 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
307 scr_printf(" from %s", sender);
309 /* Remote node, if any */
310 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
311 scr_printf(" @%s", node);
315 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
318 /* when running in curses mode, the scroll bar in most
319 xterm-style programs becomes useless, so it makes sense to
320 pause after a screenful of pages if the user has been idle
321 for a while. However, this is annoying to some of the users
322 who aren't in curses mode and tend to leave their clients
323 idle. keepalives become disabled, resulting in getting booted
324 when coming back to the idle session. but they probably have
325 a working scrollback in their terminal, so disable it in this
328 if (!is_curses_enabled())
331 scr_printf("\n---\n");
338 void set_keepalives(int s)
340 keepalives_enabled = (char) s;
344 * This loop handles the "keepalive" messages sent to the server when idling.
347 static time_t idlet = 0;
348 static void really_do_keepalive(void) {
349 int r; /* IPC response code */
353 /* This may sometimes get called before we are actually connected
354 * to the server. Don't do anything if we aren't connected. -IO
356 if (!ipc_for_signal_handlers)
359 /* If full keepalives are enabled, send a NOOP to the server and
360 * wait for a response.
362 if (keepalives_enabled == KA_YES) {
363 r = CtdlIPCNoop(ipc_for_signal_handlers);
364 if (instant_msgs > 0) {
365 if (ok_to_interrupt == 1) {
366 scr_printf("\r%64s\r", "");
368 scr_printf("%s%c ", room_name,
369 room_prompt(room_flags));
375 /* If half keepalives are enabled, send a QNOP to the server (if the
376 * server supports it) and then do nothing.
378 if ( (keepalives_enabled == KA_HALF)
379 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
380 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
384 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
385 encapsulated interface; in theory there should be no need to touch these
386 globals outside of the async_ka_* functions. */
388 #ifdef THREADED_CLIENT
389 static pthread_t ka_thr_handle;
390 static int ka_thr_active = 0;
391 static int async_ka_enabled = 0;
393 static void *ka_thread(void *arg)
395 really_do_keepalive();
396 pthread_detach(ka_thr_handle);
401 /* start up a thread to handle a keepalive in the background */
402 static void async_ka_exec(void)
404 if (!ka_thr_active) {
406 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
407 perror("pthread_create");
412 #endif /* THREADED_CLIENT */
414 /* I changed this from static to not because I need to call it from
415 screen.c, either that or make something in screen.c not static.
416 Fix it how you like. Why all the staticness? stu */
418 void do_keepalive(void)
423 if ((now - idlet) < ((long) S_KEEPALIVE))
426 /* Do a space-backspace to keep telnet sessions from idling out */
427 scr_printf(" %c", 8);
430 #ifdef THREADED_CLIENT
431 if (async_ka_enabled)
435 really_do_keepalive();
439 /* Now the actual async-keepalve API that we expose to higher levels:
440 async_ka_start() and async_ka_end(). These do nothing when we don't have
441 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
443 /* wait for a background keepalive to complete. this must be done before
444 attempting any further server requests! */
445 void async_ka_end(void)
447 #ifdef THREADED_CLIENT
449 pthread_join(ka_thr_handle, NULL);
455 /* tell do_keepalive() that keepalives are asynchronous. */
456 void async_ka_start(void)
458 #ifdef THREADED_CLIENT
465 { /* get a character from the keyboard, with */
466 int a; /* the watchdog timer in effect if necessary */
476 /* This loop waits for keyboard input. If the keepalive
477 * timer expires, it sends a keepalive to the server if
478 * necessary and then waits again.
481 scr_set_windowsize(ipc_for_signal_handlers);
483 scr_set_windowsize(ipc_for_signal_handlers);
487 tv.tv_sec = S_KEEPALIVE;
490 select(1, &rfds, NULL, NULL, &tv);
491 } while (!FD_ISSET(0, &rfds));
493 /* At this point, there's input, so fetch it.
494 * (There's a hole in the bucket...)
496 a = scr_getc(SCR_BLOCK);
503 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
504 && ((a < 32) || (a > 126)))
507 #ifndef DISABLE_CURSES
508 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
521 { /* Returns 1 for yes, 0 for no */
537 /* Returns 1 for yes, 0 for no, arg is default value */
560 /* Gets a line from the terminal */
561 /* string == Pointer to string buffer */
562 /* lim == Maximum length - if negative, no-show */
563 void getline(char *string, int lim)
577 if ((a == 8 || a == 23) && (strlen(string) == 0))
579 if ((a != 10) && (a != 8) && (strlen(string) == lim))
581 if ((a == 8) && (string[0] != 0)) {
582 string[strlen(string) - 1] = 0;
583 scr_putc(8); scr_putc(32); scr_putc(8);
586 if ((a == 23) && (string[0] != 0)) {
588 string[strlen(string) - 1] = 0;
589 scr_putc(8); scr_putc(32); scr_putc(8);
590 } while (strlen(string) && string[strlen(string) - 1] != ' ');
612 * strprompt() - prompt for a string, print the existing value and
613 * allow the user to press return to keep it...
615 void strprompt(char *prompt, char *str, int len)
622 scr_printf("%s ", prompt);
625 color(BRIGHT_MAGENTA);
628 scr_printf("%s", str);
631 for (i=0; i<strlen(str); ++i) {
648 * boolprompt() - prompt for a yes/no, print the existing value and
649 * allow the user to press return to keep it...
651 int boolprompt(char *prompt, int prev_val)
656 scr_printf("%s ", prompt);
659 color(BRIGHT_MAGENTA);
660 scr_printf("%s", (prev_val ? "Yes" : "No"));
664 r = (yesno_d(prev_val));
670 * intprompt() - like strprompt(), except for an integer
671 * (note that it RETURNS the new value!)
673 int intprompt(char *prompt, int ival, int imin, int imax)
681 snprintf(buf, sizeof buf, "%d", i);
682 strprompt(prompt, buf, 15);
684 for (p=0; p<strlen(buf); ++p) {
685 if ( (!isdigit(buf[p]))
686 && ( (buf[p]!='-') || (p!=0) ) )
690 scr_printf("*** Must be no less than %d.\n", imin);
692 scr_printf("*** Must be no more than %d.\n", imax);
693 } while ((i < imin) || (i > imax));
698 * newprompt() - prompt for a string with no existing value
699 * (clears out string buffer first)
701 void newprompt(char *prompt, char *str, int len)
703 color(BRIGHT_MAGENTA);
704 scr_printf("%s", prompt);
712 { /* returns a lower case value */
721 * parse the citadel.rc file
723 void load_command_set(void)
727 char editor_key[100];
729 struct citcmd *lastcmd = NULL;
735 /* first, set up some defaults for non-required variables */
737 for (i = 0; i < MAX_EDITORS; i++)
738 strcpy(editor_paths[i], "");
739 strcpy(printcmd, "");
740 strcpy(imagecmd, "");
741 strcpy(rc_username, "");
742 strcpy(rc_password, "");
745 rc_allow_attachments = 0;
746 rc_remember_passwords = 0;
747 strcpy(rc_exp_cmd, "");
748 rc_display_message_numbers = 0;
749 rc_force_mail_prompts = 0;
752 strcpy(rc_url_cmd, "");
753 strcpy(rc_gotmail_cmd, "");
755 rc_encrypt = RC_DEFAULT;
757 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
758 rc_screen = RC_DEFAULT;
760 rc_alt_semantics = 0;
762 /* now try to open the citadel.rc file */
765 if (getenv("HOME") != NULL) {
766 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
767 ccfile = fopen(buf, "r");
769 if (ccfile == NULL) {
770 snprintf(buf, sizeof buf,
777 ccfile = fopen(buf, "r");
779 if (ccfile == NULL) {
780 ccfile = fopen("/etc/citadel.rc", "r");
782 if (ccfile == NULL) {
783 ccfile = fopen("./citadel.rc", "r");
785 if (ccfile == NULL) {
786 perror("commands: cannot open citadel.rc");
789 while (fgets(buf, sizeof buf, ccfile) != NULL) {
790 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
791 buf[strlen(buf) - 1] = 0;
793 if (!strncasecmp(buf, "encrypt=", 8)) {
794 if (!strcasecmp(&buf[8], "yes")) {
798 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
803 else if (!strcasecmp(&buf[8], "no")) {
806 else if (!strcasecmp(&buf[8], "default")) {
807 rc_encrypt = RC_DEFAULT;
812 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
813 if (!strncasecmp(buf, "fullscreen=", 11)) {
814 if (!strcasecmp(&buf[11], "yes"))
816 else if (!strcasecmp(&buf[11], "no"))
821 if (!strncasecmp(buf, "editor=", 7))
822 strcpy(editor_paths[0], &buf[7]);
824 for (i = 0; i < MAX_EDITORS; i++)
826 sprintf(editor_key, "editor%d=", i);
827 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
828 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
831 if (!strncasecmp(buf, "printcmd=", 9))
832 strcpy(printcmd, &buf[9]);
834 if (!strncasecmp(buf, "imagecmd=", 9))
835 strcpy(imagecmd, &buf[9]);
837 if (!strncasecmp(buf, "expcmd=", 7))
838 strcpy(rc_exp_cmd, &buf[7]);
840 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
841 have_xterm = (char) atoi(&buf[24]);
843 if (!strncasecmp(buf, "use_floors=", 11)) {
844 if (!strcasecmp(&buf[11], "yes"))
845 rc_floor_mode = RC_YES;
846 if (!strcasecmp(&buf[11], "no"))
847 rc_floor_mode = RC_NO;
848 if (!strcasecmp(&buf[11], "default"))
849 rc_floor_mode = RC_DEFAULT;
851 if (!strncasecmp(buf, "beep=", 5)) {
852 rc_exp_beep = atoi(&buf[5]);
854 if (!strncasecmp(buf, "allow_attachments=", 18)) {
855 rc_allow_attachments = atoi(&buf[18]);
857 if (!strncasecmp(buf, "idle_threshold=", 15)) {
858 rc_idle_threshold = atol(&buf[15]);
860 if (!strncasecmp(buf, "remember_passwords=", 19)) {
861 rc_remember_passwords = atoi(&buf[19]);
863 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
864 rc_display_message_numbers = atoi(&buf[24]);
866 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
867 rc_force_mail_prompts = atoi(&buf[19]);
869 if (!strncasecmp(buf, "ansi_color=", 11)) {
870 if (!strncasecmp(&buf[11], "on", 2))
872 if (!strncasecmp(&buf[11], "auto", 4))
873 rc_ansi_color = 2; /* autodetect */
874 if (!strncasecmp(&buf[11], "user", 4))
875 rc_ansi_color = 3; /* user config */
877 if (!strncasecmp(buf, "use_background=", 15)) {
878 if (!strncasecmp(&buf[15], "on", 2))
881 if (!strncasecmp(buf, "prompt_control=", 15)) {
882 if (!strncasecmp(&buf[15], "on", 2))
883 rc_prompt_control = 1;
884 if (!strncasecmp(&buf[15], "user", 4))
885 rc_prompt_control = 3; /* user config */
887 if (!strncasecmp(buf, "username=", 9))
888 strcpy(rc_username, &buf[9]);
890 if (!strncasecmp(buf, "password=", 9))
891 strcpy(rc_password, &buf[9]);
893 if (!strncasecmp(buf, "urlcmd=", 7))
894 strcpy(rc_url_cmd, &buf[7]);
896 if (!strncasecmp(buf, "gotmailcmd=", 11))
897 strcpy(rc_gotmail_cmd, &buf[11]);
899 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
900 if (!strncasecmp(&buf[20], "yes", 3)) {
901 rc_alt_semantics = 1;
904 rc_alt_semantics = 0;
908 if (!strncasecmp(buf, "cmd=", 4)) {
909 strcpy(buf, &buf[4]);
911 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
913 cptr->c_cmdnum = atoi(buf);
914 for (d = strlen(buf); d >= 0; --d)
917 strcpy(buf, &buf[b + 1]);
919 cptr->c_axlevel = atoi(buf);
920 for (d = strlen(buf); d >= 0; --d)
923 strcpy(buf, &buf[b + 1]);
925 for (a = 0; a < 5; ++a)
926 cptr->c_keys[a][0] = 0;
930 buf[strlen(buf) + 1] = 0;
931 while (strlen(buf) > 0) {
933 for (d = strlen(buf); d >= 0; --d)
936 strncpy(cptr->c_keys[a], buf, b);
937 cptr->c_keys[a][b] = 0;
939 strcpy(buf, &buf[b + 1]);
949 lastcmd->next = cptr;
959 * return the key associated with a command
961 char keycmd(char *cmdstr)
965 for (a = 0; a < strlen(cmdstr); ++a)
966 if (cmdstr[a] == '&')
967 return (tolower(cmdstr[a + 1]));
973 * Output the string from a key command without the ampersand
974 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
976 char *cmd_expand(char *strbuf, int mode)
984 for (a = 0; a < strlen(exp); ++a) {
985 if (strbuf[a] == '&') {
988 strcpy(&exp[a], &exp[a + 1]);
992 strcpy(buf, &exp[a + 2]);
998 if (!strncmp(&exp[a], "^r", 2)) {
1000 strcpy(&exp[a], room_name);
1001 strcat(exp, &buf[a + 2]);
1003 if (!strncmp(&exp[a], "^c", 2)) {
1005 strcpy(&exp[a + 1], &exp[a + 2]);
1015 * Comparison function to determine if entered commands match a
1016 * command loaded from the config file.
1018 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1029 for (a = 0; a < ncomp; ++a) {
1030 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1031 || (cptr->c_axlevel > cmdax))
1039 * This function returns 1 if a given command requires a string input
1041 int requires_string(struct citcmd *cptr, int ncomp)
1046 strcpy(buf, cptr->c_keys[ncomp - 1]);
1047 for (a = 0; a < strlen(buf); ++a) {
1056 * Input a command at the main prompt.
1057 * This function returns an integer command number. If the command prompts
1058 * for a string then it is placed in the supplied buffer.
1060 int getcmd(CtdlIPC *ipc, char *argbuf)
1069 struct citcmd *cptr;
1072 * Starting a new command now, so set sigcaught to 0. This variable
1073 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1074 * been interrupted by a keypress.
1078 /* Switch color support on or off if we're in user mode */
1079 if (rc_ansi_color == 3) {
1080 if (userflags & US_COLOR)
1085 /* if we're running in idiot mode, display a cute little menu */
1086 IFNEXPERT formout(ipc, "mainmenu");
1091 for (a = 0; a < 5; ++a)
1093 /* now the room prompt... */
1094 ok_to_interrupt = 1;
1095 color(BRIGHT_WHITE);
1096 scr_printf("\n%s", room_name);
1098 scr_printf("%c ", room_prompt(room_flags));
1103 ok_to_interrupt = 0;
1105 /* Handle the backspace key, but only if there's something
1106 * to backspace over...
1108 if ((ch == 8) && (cmdpos > 0)) {
1109 back(cmdspaces[cmdpos - 1] + 1);
1113 /* Spacebar invokes "lazy traversal" commands */
1114 if ((ch == 32) && (cmdpos == 0)) {
1115 this_lazy_cmd = next_lazy_cmd;
1116 if (this_lazy_cmd == 13)
1118 if (this_lazy_cmd == 5)
1120 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1121 if (cptr->c_cmdnum == this_lazy_cmd) {
1122 for (a = 0; a < 5; ++a)
1123 if (cptr->c_keys[a][0] != 0)
1124 scr_printf("%s ", cmd_expand(
1125 cptr->c_keys[a], 0));
1127 return (this_lazy_cmd);
1131 return (this_lazy_cmd);
1133 /* Otherwise, process the command */
1134 cmdbuf[cmdpos] = tolower(ch);
1136 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1137 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1139 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1140 cmdspaces[cmdpos] = strlen(
1141 cmd_expand(cptr->c_keys[cmdpos], 0));
1143 if ((cptr->c_keys[cmdpos + 1]) != 0)
1149 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1150 if (cmdmatch(cmdbuf, cptr, 5)) {
1151 /* We've found our command. */
1152 if (requires_string(cptr, cmdpos)) {
1153 getline(argbuf, 32);
1158 /* If this command is one that changes rooms,
1159 * then the next lazy-command (space bar)
1160 * should be "read new" instead of "goto"
1162 if ((cptr->c_cmdnum == 5)
1163 || (cptr->c_cmdnum == 6)
1164 || (cptr->c_cmdnum == 47)
1165 || (cptr->c_cmdnum == 52)
1166 || (cptr->c_cmdnum == 16)
1167 || (cptr->c_cmdnum == 20))
1170 /* If this command is "read new"
1171 * then the next lazy-command (space bar)
1174 if (cptr->c_cmdnum == 13)
1177 return (cptr->c_cmdnum);
1183 pprintf("\rOne of ... \n");
1184 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1185 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1186 for (a = 0; a < 5; ++a) {
1187 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1193 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1195 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1196 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1197 for (a = 0; a < cmdpos; ++a) {
1199 cmd_expand(cptr->c_keys[a], 0));
1214 * set tty modes. commands are:
1216 * 01- set to Citadel mode
1217 * 2 - save current settings for later restoral
1218 * 3 - restore saved settings
1220 #ifdef HAVE_TERMIOS_H
1221 void stty_ctdl(int cmd)
1222 { /* SysV version of stty_ctdl() */
1223 struct termios live;
1224 static struct termios saved_settings;
1225 static int last_cmd = 0;
1232 if ((cmd == 0) || (cmd == 1)) {
1233 tcgetattr(0, &live);
1234 live.c_iflag = ISTRIP | IXON | IXANY;
1235 live.c_oflag = OPOST | ONLCR;
1236 live.c_lflag = ISIG | NOFLSH;
1238 live.c_cc[VINTR] = 0;
1239 live.c_cc[VQUIT] = 0;
1242 live.c_cc[VMIN] = 0;
1243 live.c_cc[VTIME] = 0;
1246 /* do we even need this stuff anymore? */
1247 /* live.c_line=0; */
1248 live.c_cc[VERASE] = 8;
1249 live.c_cc[VKILL] = 24;
1250 live.c_cc[VEOF] = 1;
1251 live.c_cc[VEOL] = 255;
1252 live.c_cc[VEOL2] = 0;
1253 live.c_cc[VSTART] = 0;
1254 tcsetattr(0, TCSADRAIN, &live);
1257 tcgetattr(0, &saved_settings);
1260 tcsetattr(0, TCSADRAIN, &saved_settings);
1265 void stty_ctdl(int cmd)
1266 { /* BSD version of stty_ctdl() */
1268 static struct sgttyb saved_settings;
1269 static int last_cmd = 0;
1276 if ((cmd == 0) || (cmd == 1)) {
1278 live.sg_flags |= CBREAK;
1279 live.sg_flags |= CRMOD;
1280 live.sg_flags |= NL1;
1281 live.sg_flags &= ~ECHO;
1283 live.sg_flags |= NOFLSH;
1287 gtty(0, &saved_settings);
1290 stty(0, &saved_settings);
1297 * display_help() - help file viewer
1299 void display_help(CtdlIPC *ipc, char *name)
1306 * fmout() - Citadel text formatter and paginator
1309 int width, /* screen width to use */
1310 FILE *fpin, /* file to read from, or NULL to format given text */
1311 char *text, /* text to be formatted (when fpin is NULL */
1312 FILE *fpout, /* file to write to, or NULL to write to screen */
1313 char pagin, /* nonzero if we should use the paginator */
1314 int height, /* screen height to use */
1315 int starting_lp,/* starting value for lines_printed, -1 for global */
1316 int subst) /* nonzero if we should use hypertext mode */
1318 char *buffer = NULL; /* The current message */
1319 char *word = NULL; /* What we are about to actually print */
1320 char *e; /* Pointer to position in text */
1321 char old = 0; /* The previous character */
1322 int column = 0; /* Current column */
1323 size_t i; /* Generic counter */
1325 /* Space for a single word, which can be at most screenwidth */
1326 word = (char *)calloc(1, width);
1328 err_printf("Can't alloc memory to print message: %s!\n",
1333 /* Read the entire message body into memory */
1335 buffer = load_message_from_file(fpin);
1337 err_printf("Can't print message: %s!\n",
1346 if (starting_lp >= 0)
1347 lines_printed = starting_lp;
1349 /* Run the message body */
1351 /* Catch characters that shouldn't be there at all */
1356 /* First, are we looking at a newline? */
1359 if (*e == ' ') { /* Paragraph */
1361 fprintf(fpout, "\n");
1365 lines_printed = checkpagin(lines_printed, pagin, height);
1368 } else if (old != ' ') {/* Don't print two spaces */
1370 fprintf(fpout, " ");
1379 /* Are we looking at a nonprintable? */
1380 if ( (*e < 32) || (*e > 126) ) {
1384 /* Or are we looking at a space? */
1387 if (column >= width - 1) {
1388 /* Are we in the rightmost column? */
1390 fprintf(fpout, "\n");
1394 lines_printed = checkpagin(lines_printed, pagin, height);
1397 } else if (!(column == 0 && old == ' ')) {
1398 /* Eat only the first space on a line */
1400 fprintf(fpout, " ");
1406 /* ONLY eat the FIRST space on a line */
1412 /* Read a word, slightly messy */
1415 if (!isprint(e[i]) && !isspace(e[i]))
1422 /* We should never see these, but... slightly messy */
1423 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1426 /* Break up really long words */
1427 /* TODO: auto-hyphenation someday? */
1430 strncpy(word, e, i);
1433 /* Decide where to print the word */
1434 if (column + i >= width) {
1435 /* Wrap to the next line */
1437 fprintf(fpout, "\n");
1441 lines_printed = checkpagin(lines_printed, pagin, height);
1446 /* Print the word */
1448 fprintf(fpout, "%s", word);
1450 scr_printf("%s", word);
1453 e += i; /* Start over with the whitepsace! */
1457 if (fpin) /* We allocated this, remember? */
1460 /* Is this necessary? It makes the output kind of spacey. */
1462 fprintf(fpout, "\n");
1466 lines_printed = checkpagin(lines_printed, pagin, height);
1474 * support ANSI color if defined
1476 void color(int colornum)
1478 static int hold_color;
1479 static int current_color;
1481 if (colornum == COLOR_PUSH) {
1482 hold_color = current_color;
1486 if (colornum == COLOR_POP) {
1491 current_color = colornum;
1493 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1494 if (scr_color(colornum))
1497 /* When switching to dim white, actually output an 'original
1498 * pair' sequence -- this looks better on black-on-white
1499 * terminals. - Changed to ORIGINAL_PAIR as this actually
1500 * wound up looking horrible on black-on-white terminals, not
1501 * to mention transparent terminals.
1503 if (colornum == ORIGINAL_PAIR)
1504 printf("\033[0;39;49m");
1506 printf("\033[%d;3%d;4%dm",
1507 (colornum & 8) ? 1 : 0,
1515 void cls(int colornum)
1518 printf("\033[4%dm\033[2J\033[H\033[0m",
1519 colornum ? colornum : rc_color_use_bg);
1526 * Detect whether ANSI color is available (answerback)
1528 void send_ansi_detect(void)
1530 if (rc_ansi_color == 2) {
1537 void look_for_ansi(void)
1545 if (rc_ansi_color == 0) {
1547 } else if (rc_ansi_color == 1) {
1549 } else if (rc_ansi_color == 2) {
1551 /* otherwise, do the auto-detect */
1556 if ((now - AnsiDetect) < 2)
1565 select(1, &rfds, NULL, NULL, &tv);
1566 if (FD_ISSET(0, &rfds)) {
1567 abuf[strlen(abuf) + 1] = 0;
1568 read(0, &abuf[strlen(abuf)], 1);
1570 } while (FD_ISSET(0, &rfds));
1572 for (a = 0; a < strlen(abuf); ++a) {
1573 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1574 && (abuf[a + 2] == '?')) {
1583 * Display key options (highlight hotkeys inside angle brackets)
1585 void keyopt(char *buf) {
1589 for (i=0; i<strlen(buf); ++i) {
1592 color(BRIGHT_MAGENTA);
1606 * Present a key-menu line choice type of thing
1608 char keymenu(char *menuprompt, char *menustring) {
1614 int display_prompt = 1;
1616 choices = num_tokens(menustring, '|');
1618 if (menuprompt != NULL) do_prompt = 1;
1619 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1622 if (display_prompt) {
1624 scr_printf("%s ", menuprompt);
1627 for (i=0; i<choices; ++i) {
1628 extract_token(buf, menustring, i, '|', sizeof buf);
1638 if ( (do_prompt) && (ch=='?') ) {
1639 scr_printf("\rOne of... ");
1641 for (i=0; i<choices; ++i) {
1642 extract_token(buf, menustring, i, '|', sizeof buf);
1651 for (i=0; i<choices; ++i) {
1652 extract_token(buf, menustring, i, '|', sizeof buf);
1653 for (c=1; c<strlen(buf); ++c) {
1654 if ( (ch == tolower(buf[c]))
1656 && (buf[c+1]=='>') ) {
1657 for (a=0; a<strlen(buf); ++a) {
1658 if ( (a!=(c-1)) && (a!=(c+1))) {