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.h"
70 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
74 char rc_exp_cmd[1024];
75 int rc_allow_attachments;
76 int rc_display_message_numbers;
77 int rc_force_mail_prompts;
78 int rc_remember_passwords;
81 int rc_prompt_control = 0;
82 time_t rc_idle_threshold = (time_t)900;
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 instant 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);
162 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
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; !IsEmptyStr(&buf[i]); ++i) {
194 lines_printed = checkpagin(lines_printed,
195 (userflags & US_PAGINATOR),
204 * print_instant() - print instant messages if there are any
206 void print_instant(void)
215 char *listing = NULL;
216 int r; /* IPC result code */
218 if (instant_msgs == 0)
224 if (IsEmptyStr(rc_exp_cmd)) {
229 while (instant_msgs != 0) {
230 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
234 instant_msgs = extract_int(buf, 0);
235 timestamp = extract_long(buf, 1);
236 flags = extract_int(buf, 2);
237 extract_token(sender, buf, 3, '|', sizeof sender);
238 extract_token(node, buf, 4, '|', sizeof node);
239 strcpy(last_paged, sender);
241 localtime_r(×tamp, &stamp);
243 /* If the page is a Logoff Request, honor it. */
249 if (!IsEmptyStr(rc_exp_cmd)) {
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(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
276 fprintf(outpipe, " @%s", node);
277 fprintf(outpipe, ":\n%s\n", listing);
279 if (instant_msgs == 0)
284 /* fall back to built-in instant 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(ipc_for_signal_handlers->ServInfo.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 /* This may sometimes get called before we are actually connected
356 * to the server. Don't do anything if we aren't connected. -IO
358 if (!ipc_for_signal_handlers)
361 /* If full keepalives are enabled, send a NOOP to the server and
362 * wait for a response.
364 if (keepalives_enabled == KA_YES) {
365 r = CtdlIPCNoop(ipc_for_signal_handlers);
366 if (instant_msgs > 0) {
367 if (ok_to_interrupt == 1) {
368 scr_printf("\r%64s\r", "");
370 scr_printf("%s%c ", room_name,
371 room_prompt(room_flags));
377 /* If half keepalives are enabled, send a QNOP to the server (if the
378 * server supports it) and then do nothing.
380 if ( (keepalives_enabled == KA_HALF)
381 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
382 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
386 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
387 encapsulated interface; in theory there should be no need to touch these
388 globals outside of the async_ka_* functions. */
390 #ifdef THREADED_CLIENT
391 static pthread_t ka_thr_handle;
392 static int ka_thr_active = 0;
393 static int async_ka_enabled = 0;
395 static void *ka_thread(void *arg)
397 #ifdef HAVE_BACKTRACE
398 char threadName[256];
401 sprintf(threadName, "ka_Thread n");
403 // Register for tracing
404 eCrash_RegisterThread(threadName, 0);
406 really_do_keepalive();
407 pthread_detach(ka_thr_handle);
410 #ifdef HAVE_BACKTRACE
411 eCrash_UnregisterThread();
416 /* start up a thread to handle a keepalive in the background */
417 static void async_ka_exec(void)
419 if (!ka_thr_active) {
421 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
422 perror("pthread_create");
427 #endif /* THREADED_CLIENT */
429 /* I changed this from static to not because I need to call it from
430 screen.c, either that or make something in screen.c not static.
431 Fix it how you like. Why all the staticness? stu */
433 void do_keepalive(void)
438 if ((now - idlet) < ((long) S_KEEPALIVE))
441 /* Do a space-backspace to keep telnet sessions from idling out */
442 scr_printf(" %c", 8);
445 #ifdef THREADED_CLIENT
446 if (async_ka_enabled)
450 really_do_keepalive();
454 /* Now the actual async-keepalve API that we expose to higher levels:
455 async_ka_start() and async_ka_end(). These do nothing when we don't have
456 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
458 /* wait for a background keepalive to complete. this must be done before
459 attempting any further server requests! */
460 void async_ka_end(void)
462 #ifdef THREADED_CLIENT
464 pthread_join(ka_thr_handle, NULL);
470 /* tell do_keepalive() that keepalives are asynchronous. */
471 void async_ka_start(void)
473 #ifdef THREADED_CLIENT
480 { /* get a character from the keyboard, with */
481 int a; /* the watchdog timer in effect if necessary */
491 /* This loop waits for keyboard input. If the keepalive
492 * timer expires, it sends a keepalive to the server if
493 * necessary and then waits again.
496 scr_set_windowsize(ipc_for_signal_handlers);
498 scr_set_windowsize(ipc_for_signal_handlers);
502 tv.tv_sec = S_KEEPALIVE;
505 select(1, &rfds, NULL, NULL, &tv);
506 } while (!FD_ISSET(0, &rfds));
508 /* At this point, there's input, so fetch it.
509 * (There's a hole in the bucket...)
511 a = scr_getc(SCR_BLOCK);
518 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
522 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
523 && ((a < 32) || (a > 126))) {
528 #ifndef DISABLE_CURSES
529 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
542 { /* Returns 1 for yes, 0 for no */
558 /* Returns 1 for yes, 0 for no, arg is default value */
581 /* Gets a line from the terminal */
582 /* string == Pointer to string buffer */
583 /* lim == Maximum length - if negative, no-show */
584 void ctdl_getline(char *string, int lim)
598 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
599 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
601 if ((a != 10) && (a != 8) && (strlen(string) == lim))
603 if ((a == 8) && (string[0] != 0)) {
604 string[strlen(string) - 1] = 0;
605 scr_putc(8); scr_putc(32); scr_putc(8);
608 if ((a == 23) && (string[0] != 0)) {
610 string[strlen(string) - 1] = 0;
611 scr_putc(8); scr_putc(32); scr_putc(8);
612 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
634 * strprompt() - prompt for a string, print the existing value and
635 * allow the user to press return to keep it...
637 void strprompt(char *prompt, char *str, int len)
644 scr_printf("%s ", prompt);
647 color(BRIGHT_MAGENTA);
650 scr_printf("%s", str);
653 for (i=0; !IsEmptyStr(&str[i]); ++i) {
663 ctdl_getline(buf, len);
670 * boolprompt() - prompt for a yes/no, print the existing value and
671 * allow the user to press return to keep it...
673 int boolprompt(char *prompt, int prev_val)
678 scr_printf("%s ", prompt);
681 color(BRIGHT_MAGENTA);
682 scr_printf("%s", (prev_val ? "Yes" : "No"));
686 r = (yesno_d(prev_val));
692 * intprompt() - like strprompt(), except for an integer
693 * (note that it RETURNS the new value!)
695 int intprompt(char *prompt, int ival, int imin, int imax)
703 snprintf(buf, sizeof buf, "%d", i);
704 strprompt(prompt, buf, 15);
706 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
707 if ( (!isdigit(buf[p]))
708 && ( (buf[p]!='-') || (p!=0) ) )
712 scr_printf("*** Must be no less than %d.\n", imin);
714 scr_printf("*** Must be no more than %d.\n", imax);
715 } while ((i < imin) || (i > imax));
720 * newprompt() - prompt for a string with no existing value
721 * (clears out string buffer first)
723 void newprompt(char *prompt, char *str, int len)
725 color(BRIGHT_MAGENTA);
726 scr_printf("%s", prompt);
728 ctdl_getline(str, len);
734 { /* returns a lower case value */
743 * parse the citadel.rc file
745 void load_command_set(void)
749 char editor_key[100];
751 struct citcmd *lastcmd = NULL;
757 /* first, set up some defaults for non-required variables */
759 for (i = 0; i < MAX_EDITORS; i++)
760 strcpy(editor_paths[i], "");
761 strcpy(printcmd, "");
762 strcpy(imagecmd, "");
763 strcpy(rc_username, "");
764 strcpy(rc_password, "");
767 rc_allow_attachments = 0;
768 rc_remember_passwords = 0;
769 strcpy(rc_exp_cmd, "");
770 rc_display_message_numbers = 0;
771 rc_force_mail_prompts = 0;
774 strcpy(rc_url_cmd, "");
775 strcpy(rc_gotmail_cmd, "");
777 rc_encrypt = RC_DEFAULT;
779 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
780 rc_screen = RC_DEFAULT;
782 rc_alt_semantics = 0;
784 /* now try to open the citadel.rc file */
787 if (getenv("HOME") != NULL) {
788 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
789 ccfile = fopen(buf, "r");
791 if (ccfile == NULL) {
792 ccfile = fopen(file_citadel_rc, "r");
794 if (ccfile == NULL) {
795 ccfile = fopen("/etc/citadel.rc", "r");
797 if (ccfile == NULL) {
798 ccfile = fopen("./citadel.rc", "r");
800 if (ccfile == NULL) {
801 perror("commands: cannot open citadel.rc");
804 while (fgets(buf, sizeof buf, ccfile) != NULL) {
805 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
806 buf[strlen(buf) - 1] = 0;
808 if (!strncasecmp(buf, "encrypt=", 8)) {
809 if (!strcasecmp(&buf[8], "yes")) {
813 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
818 else if (!strcasecmp(&buf[8], "no")) {
821 else if (!strcasecmp(&buf[8], "default")) {
822 rc_encrypt = RC_DEFAULT;
827 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
828 if (!strncasecmp(buf, "fullscreen=", 11)) {
829 if (!strcasecmp(&buf[11], "yes"))
831 else if (!strcasecmp(&buf[11], "no"))
836 if (!strncasecmp(buf, "editor=", 7))
837 strcpy(editor_paths[0], &buf[7]);
839 for (i = 0; i < MAX_EDITORS; i++)
841 sprintf(editor_key, "editor%d=", i);
842 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
843 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
846 if (!strncasecmp(buf, "printcmd=", 9))
847 strcpy(printcmd, &buf[9]);
849 if (!strncasecmp(buf, "imagecmd=", 9))
850 strcpy(imagecmd, &buf[9]);
852 if (!strncasecmp(buf, "expcmd=", 7))
853 strcpy(rc_exp_cmd, &buf[7]);
855 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
856 have_xterm = (char) atoi(&buf[24]);
858 if (!strncasecmp(buf, "use_floors=", 11)) {
859 if (!strcasecmp(&buf[11], "yes"))
860 rc_floor_mode = RC_YES;
861 if (!strcasecmp(&buf[11], "no"))
862 rc_floor_mode = RC_NO;
863 if (!strcasecmp(&buf[11], "default"))
864 rc_floor_mode = RC_DEFAULT;
866 if (!strncasecmp(buf, "beep=", 5)) {
867 rc_exp_beep = atoi(&buf[5]);
869 if (!strncasecmp(buf, "allow_attachments=", 18)) {
870 rc_allow_attachments = atoi(&buf[18]);
872 if (!strncasecmp(buf, "idle_threshold=", 15)) {
873 rc_idle_threshold = atol(&buf[15]);
875 if (!strncasecmp(buf, "remember_passwords=", 19)) {
876 rc_remember_passwords = atoi(&buf[19]);
878 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
879 rc_display_message_numbers = atoi(&buf[24]);
881 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
882 rc_force_mail_prompts = atoi(&buf[19]);
884 if (!strncasecmp(buf, "ansi_color=", 11)) {
885 if (!strncasecmp(&buf[11], "on", 2))
887 if (!strncasecmp(&buf[11], "auto", 4))
888 rc_ansi_color = 2; /* autodetect */
889 if (!strncasecmp(&buf[11], "user", 4))
890 rc_ansi_color = 3; /* user config */
892 if (!strncasecmp(buf, "use_background=", 15)) {
893 if (!strncasecmp(&buf[15], "on", 2))
896 if (!strncasecmp(buf, "prompt_control=", 15)) {
897 if (!strncasecmp(&buf[15], "on", 2))
898 rc_prompt_control = 1;
899 if (!strncasecmp(&buf[15], "user", 4))
900 rc_prompt_control = 3; /* user config */
902 if (!strncasecmp(buf, "username=", 9))
903 strcpy(rc_username, &buf[9]);
905 if (!strncasecmp(buf, "password=", 9))
906 strcpy(rc_password, &buf[9]);
908 if (!strncasecmp(buf, "urlcmd=", 7))
909 strcpy(rc_url_cmd, &buf[7]);
911 if (!strncasecmp(buf, "gotmailcmd=", 11))
912 strcpy(rc_gotmail_cmd, &buf[11]);
914 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
915 if (!strncasecmp(&buf[20], "yes", 3)) {
916 rc_alt_semantics = 1;
919 rc_alt_semantics = 0;
923 if (!strncasecmp(buf, "cmd=", 4)) {
924 strcpy(buf, &buf[4]);
926 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
928 cptr->c_cmdnum = atoi(buf);
929 for (d = strlen(buf); d >= 0; --d)
932 strcpy(buf, &buf[b + 1]);
934 cptr->c_axlevel = atoi(buf);
935 for (d = strlen(buf); d >= 0; --d)
938 strcpy(buf, &buf[b + 1]);
940 for (a = 0; a < 5; ++a)
941 cptr->c_keys[a][0] = 0;
945 buf[strlen(buf) + 1] = 0;
946 while (!IsEmptyStr(buf)) {
948 for (d = strlen(buf); d >= 0; --d)
951 strncpy(cptr->c_keys[a], buf, b);
952 cptr->c_keys[a][b] = 0;
954 strcpy(buf, &buf[b + 1]);
964 lastcmd->next = cptr;
974 * return the key associated with a command
976 char keycmd(char *cmdstr)
980 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
981 if (cmdstr[a] == '&')
982 return (tolower(cmdstr[a + 1]));
988 * Output the string from a key command without the ampersand
989 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
991 char *cmd_expand(char *strbuf, int mode)
999 for (a = 0; exp[a]; ++a) {
1000 if (strbuf[a] == '&') {
1002 /* dont echo these non mnemonic command keys */
1003 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1006 strcpy(&exp[a], &exp[a + 1 + noecho]);
1010 strcpy(buf, &exp[a + 2]);
1016 if (!strncmp(&exp[a], "^r", 2)) {
1018 strcpy(&exp[a], room_name);
1019 strcat(exp, &buf[a + 2]);
1021 if (!strncmp(&exp[a], "^c", 2)) {
1023 strcpy(&exp[a + 1], &exp[a + 2]);
1033 * Comparison function to determine if entered commands match a
1034 * command loaded from the config file.
1036 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1047 for (a = 0; a < ncomp; ++a) {
1048 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1049 || (cptr->c_axlevel > cmdax))
1057 * This function returns 1 if a given command requires a string input
1059 int requires_string(struct citcmd *cptr, int ncomp)
1064 strcpy(buf, cptr->c_keys[ncomp - 1]);
1065 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1074 * Input a command at the main prompt.
1075 * This function returns an integer command number. If the command prompts
1076 * for a string then it is placed in the supplied buffer.
1078 int getcmd(CtdlIPC *ipc, char *argbuf)
1087 struct citcmd *cptr;
1090 * Starting a new command now, so set sigcaught to 0. This variable
1091 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1092 * been interrupted by a keypress.
1096 /* Switch color support on or off if we're in user mode */
1097 if (rc_ansi_color == 3) {
1098 if (userflags & US_COLOR)
1103 /* if we're running in idiot mode, display a cute little menu */
1104 IFNEXPERT formout(ipc, "mainmenu");
1109 for (a = 0; a < 5; ++a)
1111 /* now the room prompt... */
1112 ok_to_interrupt = 1;
1113 color(BRIGHT_WHITE);
1114 scr_printf("\n%s", room_name);
1116 scr_printf("%c ", room_prompt(room_flags));
1121 ok_to_interrupt = 0;
1123 /* Handle the backspace key, but only if there's something
1124 * to backspace over...
1126 if ((ch == 8) && (cmdpos > 0)) {
1127 back(cmdspaces[cmdpos - 1] + 1);
1131 /* Spacebar invokes "lazy traversal" commands */
1132 if ((ch == 32) && (cmdpos == 0)) {
1133 this_lazy_cmd = next_lazy_cmd;
1134 if (this_lazy_cmd == 13)
1136 if (this_lazy_cmd == 5)
1138 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1139 if (cptr->c_cmdnum == this_lazy_cmd) {
1140 for (a = 0; a < 5; ++a)
1141 if (cptr->c_keys[a][0] != 0)
1142 scr_printf("%s ", cmd_expand(
1143 cptr->c_keys[a], 0));
1145 return (this_lazy_cmd);
1149 return (this_lazy_cmd);
1151 /* Otherwise, process the command */
1152 cmdbuf[cmdpos] = tolower(ch);
1154 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1155 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1157 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1158 cmdspaces[cmdpos] = strlen(
1159 cmd_expand(cptr->c_keys[cmdpos], 0));
1161 if ((cptr->c_keys[cmdpos + 1]) != 0)
1167 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1168 if (cmdmatch(cmdbuf, cptr, 5)) {
1169 /* We've found our command. */
1170 if (requires_string(cptr, cmdpos)) {
1171 ctdl_getline(argbuf, 64);
1176 /* If this command is one that changes rooms,
1177 * then the next lazy-command (space bar)
1178 * should be "read new" instead of "goto"
1180 if ((cptr->c_cmdnum == 5)
1181 || (cptr->c_cmdnum == 6)
1182 || (cptr->c_cmdnum == 47)
1183 || (cptr->c_cmdnum == 52)
1184 || (cptr->c_cmdnum == 16)
1185 || (cptr->c_cmdnum == 20))
1188 /* If this command is "read new"
1189 * then the next lazy-command (space bar)
1192 if (cptr->c_cmdnum == 13)
1195 return (cptr->c_cmdnum);
1201 pprintf("\rOne of ... \n");
1202 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1203 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1204 for (a = 0; a < 5; ++a) {
1205 keyopt(cmd_expand(cptr->c_keys[a], 1));
1213 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1215 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1216 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1217 for (a = 0; a < cmdpos; ++a) {
1219 cmd_expand(cptr->c_keys[a], 0));
1234 * set tty modes. commands are:
1236 * 01- set to Citadel mode
1237 * 2 - save current settings for later restoral
1238 * 3 - restore saved settings
1240 #ifdef HAVE_TERMIOS_H
1241 void stty_ctdl(int cmd)
1242 { /* SysV version of stty_ctdl() */
1243 struct termios live;
1244 static struct termios saved_settings;
1245 static int last_cmd = 0;
1252 if ((cmd == 0) || (cmd == 1)) {
1253 tcgetattr(0, &live);
1254 live.c_iflag = ISTRIP | IXON | IXANY;
1255 live.c_oflag = OPOST | ONLCR;
1256 live.c_lflag = ISIG | NOFLSH;
1258 live.c_cc[VINTR] = 0;
1259 live.c_cc[VQUIT] = 0;
1262 live.c_cc[VMIN] = 0;
1263 live.c_cc[VTIME] = 0;
1266 /* do we even need this stuff anymore? */
1267 /* live.c_line=0; */
1268 live.c_cc[VERASE] = 8;
1269 live.c_cc[VKILL] = 24;
1270 live.c_cc[VEOF] = 1;
1271 live.c_cc[VEOL] = 255;
1272 live.c_cc[VEOL2] = 0;
1273 live.c_cc[VSTART] = 0;
1274 tcsetattr(0, TCSADRAIN, &live);
1277 tcgetattr(0, &saved_settings);
1280 tcsetattr(0, TCSADRAIN, &saved_settings);
1285 void stty_ctdl(int cmd)
1286 { /* BSD version of stty_ctdl() */
1288 static struct sgttyb saved_settings;
1289 static int last_cmd = 0;
1296 if ((cmd == 0) || (cmd == 1)) {
1298 live.sg_flags |= CBREAK;
1299 live.sg_flags |= CRMOD;
1300 live.sg_flags |= NL1;
1301 live.sg_flags &= ~ECHO;
1303 live.sg_flags |= NOFLSH;
1307 gtty(0, &saved_settings);
1310 stty(0, &saved_settings);
1317 * display_help() - help file viewer
1319 void display_help(CtdlIPC *ipc, char *name)
1326 * fmout() - Citadel text formatter and paginator
1329 int width, /* screen width to use */
1330 FILE *fpin, /* file to read from, or NULL to format given text */
1331 char *text, /* text to be formatted (when fpin is NULL */
1332 FILE *fpout, /* file to write to, or NULL to write to screen */
1333 char pagin, /* nonzero if we should use the paginator */
1334 int height, /* screen height to use */
1335 int starting_lp,/* starting value for lines_printed, -1 for global */
1336 int subst) /* nonzero if we should use hypertext mode */
1338 char *buffer = NULL; /* The current message */
1339 char *word = NULL; /* What we are about to actually print */
1340 char *e; /* Pointer to position in text */
1341 char old = 0; /* The previous character */
1342 int column = 0; /* Current column */
1343 size_t i; /* Generic counter */
1345 /* Space for a single word, which can be at most screenwidth */
1346 word = (char *)calloc(1, width);
1348 err_printf("Can't alloc memory to print message: %s!\n",
1353 /* Read the entire message body into memory */
1355 buffer = load_message_from_file(fpin);
1357 err_printf("Can't print message: %s!\n",
1366 if (starting_lp >= 0)
1367 lines_printed = starting_lp;
1369 /* Run the message body */
1371 /* Catch characters that shouldn't be there at all */
1376 /* First, are we looking at a newline? */
1379 if (*e == ' ') { /* Paragraph */
1381 fprintf(fpout, "\n");
1385 lines_printed = checkpagin(lines_printed, pagin, height);
1388 } else if (old != ' ') {/* Don't print two spaces */
1390 fprintf(fpout, " ");
1400 /* Are we looking at a nonprintable?
1401 * (This section is now commented out because we could be displaying
1402 * a character set like UTF-8 or ISO-8859-1.)
1403 if ( (*e < 32) || (*e > 126) ) {
1408 /* Or are we looking at a space? */
1411 if (column >= width - 1) {
1412 /* Are we in the rightmost column? */
1414 fprintf(fpout, "\n");
1418 lines_printed = checkpagin(lines_printed, pagin, height);
1421 } else if (!(column == 0 && old == ' ')) {
1422 /* Eat only the first space on a line */
1424 fprintf(fpout, " ");
1430 /* ONLY eat the FIRST space on a line */
1436 /* Read a word, slightly messy */
1439 if (!isprint(e[i]) && !isspace(e[i]))
1446 /* We should never see these, but... slightly messy */
1447 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1450 /* Break up really long words */
1451 /* TODO: auto-hyphenation someday? */
1454 strncpy(word, e, i);
1457 /* Decide where to print the word */
1458 if (column + i >= width) {
1459 /* Wrap to the next line */
1461 fprintf(fpout, "\n");
1465 lines_printed = checkpagin(lines_printed, pagin, height);
1470 /* Print the word */
1472 fprintf(fpout, "%s", word);
1474 scr_printf("%s", word);
1477 e += i; /* Start over with the whitepsace! */
1481 if (fpin) /* We allocated this, remember? */
1484 /* Is this necessary? It makes the output kind of spacey. */
1486 fprintf(fpout, "\n");
1490 lines_printed = checkpagin(lines_printed, pagin, height);
1498 * support ANSI color if defined
1500 void color(int colornum)
1502 static int hold_color;
1503 static int current_color;
1505 if (colornum == COLOR_PUSH) {
1506 hold_color = current_color;
1510 if (colornum == COLOR_POP) {
1515 current_color = colornum;
1517 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1518 if (scr_color(colornum))
1521 /* When switching to dim white, actually output an 'original
1522 * pair' sequence -- this looks better on black-on-white
1523 * terminals. - Changed to ORIGINAL_PAIR as this actually
1524 * wound up looking horrible on black-on-white terminals, not
1525 * to mention transparent terminals.
1527 if (colornum == ORIGINAL_PAIR)
1528 printf("\033[0;39;49m");
1530 printf("\033[%d;3%d;4%dm",
1531 (colornum & 8) ? 1 : 0,
1539 void cls(int colornum)
1542 printf("\033[4%dm\033[2J\033[H\033[0m",
1543 colornum ? colornum : rc_color_use_bg);
1550 * Detect whether ANSI color is available (answerback)
1552 void send_ansi_detect(void)
1554 if (rc_ansi_color == 2) {
1561 void look_for_ansi(void)
1569 if (rc_ansi_color == 0) {
1571 } else if (rc_ansi_color == 1) {
1573 } else if (rc_ansi_color == 2) {
1575 /* otherwise, do the auto-detect */
1580 if ((now - AnsiDetect) < 2)
1589 select(1, &rfds, NULL, NULL, &tv);
1590 if (FD_ISSET(0, &rfds)) {
1591 abuf[strlen(abuf) + 1] = 0;
1592 read(0, &abuf[strlen(abuf)], 1);
1594 } while (FD_ISSET(0, &rfds));
1596 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1597 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1598 && (abuf[a + 2] == '?')) {
1607 * Display key options (highlight hotkeys inside angle brackets)
1609 void keyopt(char *buf) {
1613 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1615 pprintf("%c", buf[i]);
1616 color(BRIGHT_MAGENTA);
1618 if (buf[i]=='>'&& buf[i+1] != '>') {
1621 pprintf("%c", buf[i]);
1630 * Present a key-menu line choice type of thing
1632 char keymenu(char *menuprompt, char *menustring) {
1638 int display_prompt = 1;
1640 choices = num_tokens(menustring, '|');
1642 if (menuprompt != NULL) do_prompt = 1;
1643 if (menuprompt != NULL) if (IsEmptyStr(menuprompt)) do_prompt = 0;
1646 if (display_prompt) {
1648 scr_printf("%s ", menuprompt);
1651 for (i=0; i<choices; ++i) {
1652 extract_token(buf, menustring, i, '|', sizeof buf);
1662 if ( (do_prompt) && (ch=='?') ) {
1663 scr_printf("\rOne of... ");
1665 for (i=0; i<choices; ++i) {
1666 extract_token(buf, menustring, i, '|', sizeof buf);
1675 for (i=0; i<choices; ++i) {
1676 extract_token(buf, menustring, i, '|', sizeof buf);
1677 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1678 if ( (ch == tolower(buf[c]))
1680 && (buf[c+1]=='>') ) {
1681 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1682 if ( (a!=(c-1)) && (a!=(c+1))) {