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 rc_reply_extedit = 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 snprintf(buf, sizeof buf,
778 ccfile = fopen(buf, "r");
780 if (ccfile == NULL) {
781 ccfile = fopen("/etc/citadel.rc", "r");
783 if (ccfile == NULL) {
784 ccfile = fopen("./citadel.rc", "r");
786 if (ccfile == NULL) {
787 perror("commands: cannot open citadel.rc");
790 while (fgets(buf, sizeof buf, ccfile) != NULL) {
791 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
792 buf[strlen(buf) - 1] = 0;
794 if (!strncasecmp(buf, "encrypt=", 8)) {
795 if (!strcasecmp(&buf[8], "yes")) {
799 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
804 else if (!strcasecmp(&buf[8], "no")) {
807 else if (!strcasecmp(&buf[8], "default")) {
808 rc_encrypt = RC_DEFAULT;
813 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
814 if (!strncasecmp(buf, "fullscreen=", 11)) {
815 if (!strcasecmp(&buf[11], "yes"))
817 else if (!strcasecmp(&buf[11], "no"))
822 if (!strncasecmp(buf, "editor=", 7))
823 strcpy(editor_paths[0], &buf[7]);
825 for (i = 0; i < MAX_EDITORS; i++)
827 sprintf(editor_key, "editor%d=", i);
828 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
829 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
832 if (!strncasecmp(buf, "printcmd=", 9))
833 strcpy(printcmd, &buf[9]);
835 if (!strncasecmp(buf, "imagecmd=", 9))
836 strcpy(imagecmd, &buf[9]);
838 if (!strncasecmp(buf, "expcmd=", 7))
839 strcpy(rc_exp_cmd, &buf[7]);
841 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
842 have_xterm = (char) atoi(&buf[24]);
844 if (!strncasecmp(buf, "use_floors=", 11)) {
845 if (!strcasecmp(&buf[11], "yes"))
846 rc_floor_mode = RC_YES;
847 if (!strcasecmp(&buf[11], "no"))
848 rc_floor_mode = RC_NO;
849 if (!strcasecmp(&buf[11], "default"))
850 rc_floor_mode = RC_DEFAULT;
852 if (!strncasecmp(buf, "beep=", 5)) {
853 rc_exp_beep = atoi(&buf[5]);
855 if (!strncasecmp(buf, "allow_attachments=", 18)) {
856 rc_allow_attachments = atoi(&buf[18]);
858 if (!strncasecmp(buf, "idle_threshold=", 15)) {
859 rc_idle_threshold = atol(&buf[15]);
861 if (!strncasecmp(buf, "remember_passwords=", 19)) {
862 rc_remember_passwords = atoi(&buf[19]);
864 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
865 rc_display_message_numbers = atoi(&buf[24]);
867 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
868 rc_force_mail_prompts = atoi(&buf[19]);
870 if (!strncasecmp(buf, "ansi_color=", 11)) {
871 if (!strncasecmp(&buf[11], "on", 2))
873 if (!strncasecmp(&buf[11], "auto", 4))
874 rc_ansi_color = 2; /* autodetect */
875 if (!strncasecmp(&buf[11], "user", 4))
876 rc_ansi_color = 3; /* user config */
878 if (!strncasecmp(buf, "use_background=", 15)) {
879 if (!strncasecmp(&buf[15], "on", 2))
882 if (!strncasecmp(buf, "prompt_control=", 15)) {
883 if (!strncasecmp(&buf[15], "on", 2))
884 rc_prompt_control = 1;
885 if (!strncasecmp(&buf[15], "user", 4))
886 rc_prompt_control = 3; /* user config */
888 if (!strncasecmp(buf, "username=", 9))
889 strcpy(rc_username, &buf[9]);
891 if (!strncasecmp(buf, "password=", 9))
892 strcpy(rc_password, &buf[9]);
894 if (!strncasecmp(buf, "urlcmd=", 7))
895 strcpy(rc_url_cmd, &buf[7]);
897 if (!strncasecmp(buf, "gotmailcmd=", 11))
898 strcpy(rc_gotmail_cmd, &buf[11]);
900 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
901 if (!strncasecmp(&buf[20], "yes", 3)) {
902 rc_alt_semantics = 1;
905 rc_alt_semantics = 0;
908 if (!strncasecmp(buf, "reply_with_external_editor=", 27)) {
909 if (!strncasecmp(&buf[27], "yes", 3)) {
910 rc_reply_extedit = 1;
913 rc_reply_extedit = 0;
916 if (!strncasecmp(buf, "cmd=", 4)) {
917 strcpy(buf, &buf[4]);
919 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
921 cptr->c_cmdnum = atoi(buf);
922 for (d = strlen(buf); d >= 0; --d)
925 strcpy(buf, &buf[b + 1]);
927 cptr->c_axlevel = atoi(buf);
928 for (d = strlen(buf); d >= 0; --d)
931 strcpy(buf, &buf[b + 1]);
933 for (a = 0; a < 5; ++a)
934 cptr->c_keys[a][0] = 0;
938 buf[strlen(buf) + 1] = 0;
939 while (strlen(buf) > 0) {
941 for (d = strlen(buf); d >= 0; --d)
944 strncpy(cptr->c_keys[a], buf, b);
945 cptr->c_keys[a][b] = 0;
947 strcpy(buf, &buf[b + 1]);
957 lastcmd->next = cptr;
967 * return the key associated with a command
969 char keycmd(char *cmdstr)
973 for (a = 0; a < strlen(cmdstr); ++a)
974 if (cmdstr[a] == '&')
975 return (tolower(cmdstr[a + 1]));
981 * Output the string from a key command without the ampersand
982 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
984 char *cmd_expand(char *strbuf, int mode)
992 for (a = 0; a < strlen(exp); ++a) {
993 if (strbuf[a] == '&') {
996 strcpy(&exp[a], &exp[a + 1]);
1000 strcpy(buf, &exp[a + 2]);
1006 if (!strncmp(&exp[a], "^r", 2)) {
1008 strcpy(&exp[a], room_name);
1009 strcat(exp, &buf[a + 2]);
1011 if (!strncmp(&exp[a], "^c", 2)) {
1013 strcpy(&exp[a + 1], &exp[a + 2]);
1023 * Comparison function to determine if entered commands match a
1024 * command loaded from the config file.
1026 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1037 for (a = 0; a < ncomp; ++a) {
1038 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1039 || (cptr->c_axlevel > cmdax))
1047 * This function returns 1 if a given command requires a string input
1049 int requires_string(struct citcmd *cptr, int ncomp)
1054 strcpy(buf, cptr->c_keys[ncomp - 1]);
1055 for (a = 0; a < strlen(buf); ++a) {
1064 * Input a command at the main prompt.
1065 * This function returns an integer command number. If the command prompts
1066 * for a string then it is placed in the supplied buffer.
1068 int getcmd(CtdlIPC *ipc, char *argbuf)
1077 struct citcmd *cptr;
1080 * Starting a new command now, so set sigcaught to 0. This variable
1081 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1082 * been interrupted by a keypress.
1086 /* Switch color support on or off if we're in user mode */
1087 if (rc_ansi_color == 3) {
1088 if (userflags & US_COLOR)
1093 /* if we're running in idiot mode, display a cute little menu */
1094 IFNEXPERT formout(ipc, "mainmenu");
1099 for (a = 0; a < 5; ++a)
1101 /* now the room prompt... */
1102 ok_to_interrupt = 1;
1103 color(BRIGHT_WHITE);
1104 scr_printf("\n%s", room_name);
1106 scr_printf("%c ", room_prompt(room_flags));
1111 ok_to_interrupt = 0;
1113 /* Handle the backspace key, but only if there's something
1114 * to backspace over...
1116 if ((ch == 8) && (cmdpos > 0)) {
1117 back(cmdspaces[cmdpos - 1] + 1);
1121 /* Spacebar invokes "lazy traversal" commands */
1122 if ((ch == 32) && (cmdpos == 0)) {
1123 this_lazy_cmd = next_lazy_cmd;
1124 if (this_lazy_cmd == 13)
1126 if (this_lazy_cmd == 5)
1128 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1129 if (cptr->c_cmdnum == this_lazy_cmd) {
1130 for (a = 0; a < 5; ++a)
1131 if (cptr->c_keys[a][0] != 0)
1132 scr_printf("%s ", cmd_expand(
1133 cptr->c_keys[a], 0));
1135 return (this_lazy_cmd);
1139 return (this_lazy_cmd);
1141 /* Otherwise, process the command */
1142 cmdbuf[cmdpos] = tolower(ch);
1144 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1145 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1147 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1148 cmdspaces[cmdpos] = strlen(
1149 cmd_expand(cptr->c_keys[cmdpos], 0));
1151 if ((cptr->c_keys[cmdpos + 1]) != 0)
1157 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1158 if (cmdmatch(cmdbuf, cptr, 5)) {
1159 /* We've found our command. */
1160 if (requires_string(cptr, cmdpos)) {
1161 getline(argbuf, 32);
1166 /* If this command is one that changes rooms,
1167 * then the next lazy-command (space bar)
1168 * should be "read new" instead of "goto"
1170 if ((cptr->c_cmdnum == 5)
1171 || (cptr->c_cmdnum == 6)
1172 || (cptr->c_cmdnum == 47)
1173 || (cptr->c_cmdnum == 52)
1174 || (cptr->c_cmdnum == 16)
1175 || (cptr->c_cmdnum == 20))
1178 /* If this command is "read new"
1179 * then the next lazy-command (space bar)
1182 if (cptr->c_cmdnum == 13)
1185 return (cptr->c_cmdnum);
1191 pprintf("\rOne of ... \n");
1192 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1193 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1194 for (a = 0; a < 5; ++a) {
1195 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1201 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1203 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1204 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1205 for (a = 0; a < cmdpos; ++a) {
1207 cmd_expand(cptr->c_keys[a], 0));
1222 * set tty modes. commands are:
1224 * 01- set to Citadel mode
1225 * 2 - save current settings for later restoral
1226 * 3 - restore saved settings
1228 #ifdef HAVE_TERMIOS_H
1229 void stty_ctdl(int cmd)
1230 { /* SysV version of stty_ctdl() */
1231 struct termios live;
1232 static struct termios saved_settings;
1233 static int last_cmd = 0;
1240 if ((cmd == 0) || (cmd == 1)) {
1241 tcgetattr(0, &live);
1242 live.c_iflag = ISTRIP | IXON | IXANY;
1243 live.c_oflag = OPOST | ONLCR;
1244 live.c_lflag = ISIG | NOFLSH;
1246 live.c_cc[VINTR] = 0;
1247 live.c_cc[VQUIT] = 0;
1250 live.c_cc[VMIN] = 0;
1251 live.c_cc[VTIME] = 0;
1254 /* do we even need this stuff anymore? */
1255 /* live.c_line=0; */
1256 live.c_cc[VERASE] = 8;
1257 live.c_cc[VKILL] = 24;
1258 live.c_cc[VEOF] = 1;
1259 live.c_cc[VEOL] = 255;
1260 live.c_cc[VEOL2] = 0;
1261 live.c_cc[VSTART] = 0;
1262 tcsetattr(0, TCSADRAIN, &live);
1265 tcgetattr(0, &saved_settings);
1268 tcsetattr(0, TCSADRAIN, &saved_settings);
1273 void stty_ctdl(int cmd)
1274 { /* BSD version of stty_ctdl() */
1276 static struct sgttyb saved_settings;
1277 static int last_cmd = 0;
1284 if ((cmd == 0) || (cmd == 1)) {
1286 live.sg_flags |= CBREAK;
1287 live.sg_flags |= CRMOD;
1288 live.sg_flags |= NL1;
1289 live.sg_flags &= ~ECHO;
1291 live.sg_flags |= NOFLSH;
1295 gtty(0, &saved_settings);
1298 stty(0, &saved_settings);
1305 * display_help() - help file viewer
1307 void display_help(CtdlIPC *ipc, char *name)
1314 * fmout() - Citadel text formatter and paginator
1317 int width, /* screen width to use */
1318 FILE *fpin, /* file to read from, or NULL to format given text */
1319 char *text, /* text to be formatted (when fpin is NULL */
1320 FILE *fpout, /* file to write to, or NULL to write to screen */
1321 char pagin, /* nonzero if we should use the paginator */
1322 int height, /* screen height to use */
1323 int starting_lp,/* starting value for lines_printed, -1 for global */
1324 int subst) /* nonzero if we should use hypertext mode */
1326 char *buffer = NULL; /* The current message */
1327 char *word = NULL; /* What we are about to actually print */
1328 char *e; /* Pointer to position in text */
1329 char old = 0; /* The previous character */
1330 int column = 0; /* Current column */
1331 size_t i; /* Generic counter */
1333 /* Space for a single word, which can be at most screenwidth */
1334 word = (char *)calloc(1, width);
1336 err_printf("Can't alloc memory to print message: %s!\n",
1341 /* Read the entire message body into memory */
1343 buffer = load_message_from_file(fpin);
1345 err_printf("Can't print message: %s!\n",
1354 if (starting_lp >= 0)
1355 lines_printed = starting_lp;
1357 /* Run the message body */
1359 /* Catch characters that shouldn't be there at all */
1364 /* First, are we looking at a newline? */
1367 if (*e == ' ') { /* Paragraph */
1369 fprintf(fpout, "\n");
1373 lines_printed = checkpagin(lines_printed, pagin, height);
1376 } else if (old != ' ') {/* Don't print two spaces */
1378 fprintf(fpout, " ");
1387 /* Are we looking at a nonprintable? */
1388 if ( (*e < 32) || (*e > 126) ) {
1392 /* Or are we looking at a space? */
1395 if (column >= width - 1) {
1396 /* Are we in the rightmost column? */
1398 fprintf(fpout, "\n");
1402 lines_printed = checkpagin(lines_printed, pagin, height);
1405 } else if (!(column == 0 && old == ' ')) {
1406 /* Eat only the first space on a line */
1408 fprintf(fpout, " ");
1414 /* ONLY eat the FIRST space on a line */
1420 /* Read a word, slightly messy */
1423 if (!isprint(e[i]) && !isspace(e[i]))
1430 /* We should never see these, but... slightly messy */
1431 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1434 /* Break up really long words */
1435 /* TODO: auto-hyphenation someday? */
1438 strncpy(word, e, i);
1441 /* Decide where to print the word */
1442 if (column + i >= width) {
1443 /* Wrap to the next line */
1445 fprintf(fpout, "\n");
1449 lines_printed = checkpagin(lines_printed, pagin, height);
1454 /* Print the word */
1456 fprintf(fpout, "%s", word);
1458 scr_printf("%s", word);
1461 e += i; /* Start over with the whitepsace! */
1465 if (fpin) /* We allocated this, remember? */
1468 /* Is this necessary? It makes the output kind of spacey. */
1470 fprintf(fpout, "\n");
1474 lines_printed = checkpagin(lines_printed, pagin, height);
1482 * support ANSI color if defined
1484 void color(int colornum)
1486 static int hold_color;
1487 static int current_color;
1489 if (colornum == COLOR_PUSH) {
1490 hold_color = current_color;
1494 if (colornum == COLOR_POP) {
1499 current_color = colornum;
1501 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1502 if (scr_color(colornum))
1505 /* When switching to dim white, actually output an 'original
1506 * pair' sequence -- this looks better on black-on-white
1507 * terminals. - Changed to ORIGINAL_PAIR as this actually
1508 * wound up looking horrible on black-on-white terminals, not
1509 * to mention transparent terminals.
1511 if (colornum == ORIGINAL_PAIR)
1512 printf("\033[0;39;49m");
1514 printf("\033[%d;3%d;4%dm",
1515 (colornum & 8) ? 1 : 0,
1523 void cls(int colornum)
1526 printf("\033[4%dm\033[2J\033[H\033[0m",
1527 colornum ? colornum : rc_color_use_bg);
1534 * Detect whether ANSI color is available (answerback)
1536 void send_ansi_detect(void)
1538 if (rc_ansi_color == 2) {
1545 void look_for_ansi(void)
1553 if (rc_ansi_color == 0) {
1555 } else if (rc_ansi_color == 1) {
1557 } else if (rc_ansi_color == 2) {
1559 /* otherwise, do the auto-detect */
1564 if ((now - AnsiDetect) < 2)
1573 select(1, &rfds, NULL, NULL, &tv);
1574 if (FD_ISSET(0, &rfds)) {
1575 abuf[strlen(abuf) + 1] = 0;
1576 read(0, &abuf[strlen(abuf)], 1);
1578 } while (FD_ISSET(0, &rfds));
1580 for (a = 0; a < strlen(abuf); ++a) {
1581 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1582 && (abuf[a + 2] == '?')) {
1591 * Display key options (highlight hotkeys inside angle brackets)
1593 void keyopt(char *buf) {
1597 for (i=0; i<strlen(buf); ++i) {
1600 color(BRIGHT_MAGENTA);
1614 * Present a key-menu line choice type of thing
1616 char keymenu(char *menuprompt, char *menustring) {
1622 int display_prompt = 1;
1624 choices = num_tokens(menustring, '|');
1626 if (menuprompt != NULL) do_prompt = 1;
1627 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1630 if (display_prompt) {
1632 scr_printf("%s ", menuprompt);
1635 for (i=0; i<choices; ++i) {
1636 extract_token(buf, menustring, i, '|', sizeof buf);
1646 if ( (do_prompt) && (ch=='?') ) {
1647 scr_printf("\rOne of... ");
1649 for (i=0; i<choices; ++i) {
1650 extract_token(buf, menustring, i, '|', sizeof buf);
1659 for (i=0; i<choices; ++i) {
1660 extract_token(buf, menustring, i, '|', sizeof buf);
1661 for (c=1; c<strlen(buf); ++c) {
1662 if ( (ch == tolower(buf[c]))
1664 && (buf[c+1]=='>') ) {
1665 for (a=0; a<strlen(buf); ++a) {
1666 if ( (a!=(c-1)) && (a!=(c+1))) {