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, "%s/citadel.rc", CTDLDIR);
772 ccfile = fopen(buf, "r");
774 if (ccfile == NULL) {
775 ccfile = fopen("/etc/citadel.rc", "r");
777 if (ccfile == NULL) {
778 ccfile = fopen("./citadel.rc", "r");
780 if (ccfile == NULL) {
781 perror("commands: cannot open citadel.rc");
784 while (fgets(buf, sizeof buf, ccfile) != NULL) {
785 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
786 buf[strlen(buf) - 1] = 0;
788 if (!strncasecmp(buf, "encrypt=", 8)) {
789 if (!strcasecmp(&buf[8], "yes")) {
793 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
798 else if (!strcasecmp(&buf[8], "no")) {
801 else if (!strcasecmp(&buf[8], "default")) {
802 rc_encrypt = RC_DEFAULT;
807 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
808 if (!strncasecmp(buf, "fullscreen=", 11)) {
809 if (!strcasecmp(&buf[11], "yes"))
811 else if (!strcasecmp(&buf[11], "no"))
816 if (!strncasecmp(buf, "editor=", 7))
817 strcpy(editor_paths[0], &buf[7]);
819 for (i = 0; i < MAX_EDITORS; i++)
821 sprintf(editor_key, "editor%d=", i);
822 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
823 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
826 if (!strncasecmp(buf, "printcmd=", 9))
827 strcpy(printcmd, &buf[9]);
829 if (!strncasecmp(buf, "imagecmd=", 9))
830 strcpy(imagecmd, &buf[9]);
832 if (!strncasecmp(buf, "expcmd=", 7))
833 strcpy(rc_exp_cmd, &buf[7]);
835 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
836 have_xterm = (char) atoi(&buf[24]);
838 if (!strncasecmp(buf, "use_floors=", 11)) {
839 if (!strcasecmp(&buf[11], "yes"))
840 rc_floor_mode = RC_YES;
841 if (!strcasecmp(&buf[11], "no"))
842 rc_floor_mode = RC_NO;
843 if (!strcasecmp(&buf[11], "default"))
844 rc_floor_mode = RC_DEFAULT;
846 if (!strncasecmp(buf, "beep=", 5)) {
847 rc_exp_beep = atoi(&buf[5]);
849 if (!strncasecmp(buf, "allow_attachments=", 18)) {
850 rc_allow_attachments = atoi(&buf[18]);
852 if (!strncasecmp(buf, "idle_threshold=", 15)) {
853 rc_idle_threshold = atol(&buf[15]);
855 if (!strncasecmp(buf, "remember_passwords=", 19)) {
856 rc_remember_passwords = atoi(&buf[19]);
858 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
859 rc_display_message_numbers = atoi(&buf[24]);
861 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
862 rc_force_mail_prompts = atoi(&buf[19]);
864 if (!strncasecmp(buf, "ansi_color=", 11)) {
865 if (!strncasecmp(&buf[11], "on", 2))
867 if (!strncasecmp(&buf[11], "auto", 4))
868 rc_ansi_color = 2; /* autodetect */
869 if (!strncasecmp(&buf[11], "user", 4))
870 rc_ansi_color = 3; /* user config */
872 if (!strncasecmp(buf, "use_background=", 15)) {
873 if (!strncasecmp(&buf[15], "on", 2))
876 if (!strncasecmp(buf, "prompt_control=", 15)) {
877 if (!strncasecmp(&buf[15], "on", 2))
878 rc_prompt_control = 1;
879 if (!strncasecmp(&buf[15], "user", 4))
880 rc_prompt_control = 3; /* user config */
882 if (!strncasecmp(buf, "username=", 9))
883 strcpy(rc_username, &buf[9]);
885 if (!strncasecmp(buf, "password=", 9))
886 strcpy(rc_password, &buf[9]);
888 if (!strncasecmp(buf, "urlcmd=", 7))
889 strcpy(rc_url_cmd, &buf[7]);
891 if (!strncasecmp(buf, "gotmailcmd=", 11))
892 strcpy(rc_gotmail_cmd, &buf[11]);
894 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
895 if (!strncasecmp(&buf[20], "yes", 3)) {
896 rc_alt_semantics = 1;
899 rc_alt_semantics = 0;
902 if (!strncasecmp(buf, "reply_with_external_editor=", 27)) {
903 if (!strncasecmp(&buf[27], "yes", 3)) {
904 rc_reply_extedit = 1;
907 rc_reply_extedit = 0;
910 if (!strncasecmp(buf, "cmd=", 4)) {
911 strcpy(buf, &buf[4]);
913 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
915 cptr->c_cmdnum = atoi(buf);
916 for (d = strlen(buf); d >= 0; --d)
919 strcpy(buf, &buf[b + 1]);
921 cptr->c_axlevel = atoi(buf);
922 for (d = strlen(buf); d >= 0; --d)
925 strcpy(buf, &buf[b + 1]);
927 for (a = 0; a < 5; ++a)
928 cptr->c_keys[a][0] = 0;
932 buf[strlen(buf) + 1] = 0;
933 while (strlen(buf) > 0) {
935 for (d = strlen(buf); d >= 0; --d)
938 strncpy(cptr->c_keys[a], buf, b);
939 cptr->c_keys[a][b] = 0;
941 strcpy(buf, &buf[b + 1]);
951 lastcmd->next = cptr;
961 * return the key associated with a command
963 char keycmd(char *cmdstr)
967 for (a = 0; a < strlen(cmdstr); ++a)
968 if (cmdstr[a] == '&')
969 return (tolower(cmdstr[a + 1]));
975 * Output the string from a key command without the ampersand
976 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
978 char *cmd_expand(char *strbuf, int mode)
986 for (a = 0; a < strlen(exp); ++a) {
987 if (strbuf[a] == '&') {
990 strcpy(&exp[a], &exp[a + 1]);
994 strcpy(buf, &exp[a + 2]);
1000 if (!strncmp(&exp[a], "^r", 2)) {
1002 strcpy(&exp[a], room_name);
1003 strcat(exp, &buf[a + 2]);
1005 if (!strncmp(&exp[a], "^c", 2)) {
1007 strcpy(&exp[a + 1], &exp[a + 2]);
1017 * Comparison function to determine if entered commands match a
1018 * command loaded from the config file.
1020 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1031 for (a = 0; a < ncomp; ++a) {
1032 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1033 || (cptr->c_axlevel > cmdax))
1041 * This function returns 1 if a given command requires a string input
1043 int requires_string(struct citcmd *cptr, int ncomp)
1048 strcpy(buf, cptr->c_keys[ncomp - 1]);
1049 for (a = 0; a < strlen(buf); ++a) {
1058 * Input a command at the main prompt.
1059 * This function returns an integer command number. If the command prompts
1060 * for a string then it is placed in the supplied buffer.
1062 int getcmd(CtdlIPC *ipc, char *argbuf)
1071 struct citcmd *cptr;
1074 * Starting a new command now, so set sigcaught to 0. This variable
1075 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1076 * been interrupted by a keypress.
1080 /* Switch color support on or off if we're in user mode */
1081 if (rc_ansi_color == 3) {
1082 if (userflags & US_COLOR)
1087 /* if we're running in idiot mode, display a cute little menu */
1088 IFNEXPERT formout(ipc, "mainmenu");
1093 for (a = 0; a < 5; ++a)
1095 /* now the room prompt... */
1096 ok_to_interrupt = 1;
1097 color(BRIGHT_WHITE);
1098 scr_printf("\n%s", room_name);
1100 scr_printf("%c ", room_prompt(room_flags));
1105 ok_to_interrupt = 0;
1107 /* Handle the backspace key, but only if there's something
1108 * to backspace over...
1110 if ((ch == 8) && (cmdpos > 0)) {
1111 back(cmdspaces[cmdpos - 1] + 1);
1115 /* Spacebar invokes "lazy traversal" commands */
1116 if ((ch == 32) && (cmdpos == 0)) {
1117 this_lazy_cmd = next_lazy_cmd;
1118 if (this_lazy_cmd == 13)
1120 if (this_lazy_cmd == 5)
1122 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1123 if (cptr->c_cmdnum == this_lazy_cmd) {
1124 for (a = 0; a < 5; ++a)
1125 if (cptr->c_keys[a][0] != 0)
1126 scr_printf("%s ", cmd_expand(
1127 cptr->c_keys[a], 0));
1129 return (this_lazy_cmd);
1133 return (this_lazy_cmd);
1135 /* Otherwise, process the command */
1136 cmdbuf[cmdpos] = tolower(ch);
1138 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1139 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1141 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1142 cmdspaces[cmdpos] = strlen(
1143 cmd_expand(cptr->c_keys[cmdpos], 0));
1145 if ((cptr->c_keys[cmdpos + 1]) != 0)
1151 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1152 if (cmdmatch(cmdbuf, cptr, 5)) {
1153 /* We've found our command. */
1154 if (requires_string(cptr, cmdpos)) {
1155 getline(argbuf, 32);
1160 /* If this command is one that changes rooms,
1161 * then the next lazy-command (space bar)
1162 * should be "read new" instead of "goto"
1164 if ((cptr->c_cmdnum == 5)
1165 || (cptr->c_cmdnum == 6)
1166 || (cptr->c_cmdnum == 47)
1167 || (cptr->c_cmdnum == 52)
1168 || (cptr->c_cmdnum == 16)
1169 || (cptr->c_cmdnum == 20))
1172 /* If this command is "read new"
1173 * then the next lazy-command (space bar)
1176 if (cptr->c_cmdnum == 13)
1179 return (cptr->c_cmdnum);
1185 pprintf("\rOne of ... \n");
1186 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1187 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1188 for (a = 0; a < 5; ++a) {
1189 pprintf("%s ", cmd_expand(cptr->c_keys[a], 1));
1195 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1197 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1198 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1199 for (a = 0; a < cmdpos; ++a) {
1201 cmd_expand(cptr->c_keys[a], 0));
1216 * set tty modes. commands are:
1218 * 01- set to Citadel mode
1219 * 2 - save current settings for later restoral
1220 * 3 - restore saved settings
1222 #ifdef HAVE_TERMIOS_H
1223 void stty_ctdl(int cmd)
1224 { /* SysV version of stty_ctdl() */
1225 struct termios live;
1226 static struct termios saved_settings;
1227 static int last_cmd = 0;
1234 if ((cmd == 0) || (cmd == 1)) {
1235 tcgetattr(0, &live);
1236 live.c_iflag = ISTRIP | IXON | IXANY;
1237 live.c_oflag = OPOST | ONLCR;
1238 live.c_lflag = ISIG | NOFLSH;
1240 live.c_cc[VINTR] = 0;
1241 live.c_cc[VQUIT] = 0;
1244 live.c_cc[VMIN] = 0;
1245 live.c_cc[VTIME] = 0;
1248 /* do we even need this stuff anymore? */
1249 /* live.c_line=0; */
1250 live.c_cc[VERASE] = 8;
1251 live.c_cc[VKILL] = 24;
1252 live.c_cc[VEOF] = 1;
1253 live.c_cc[VEOL] = 255;
1254 live.c_cc[VEOL2] = 0;
1255 live.c_cc[VSTART] = 0;
1256 tcsetattr(0, TCSADRAIN, &live);
1259 tcgetattr(0, &saved_settings);
1262 tcsetattr(0, TCSADRAIN, &saved_settings);
1267 void stty_ctdl(int cmd)
1268 { /* BSD version of stty_ctdl() */
1270 static struct sgttyb saved_settings;
1271 static int last_cmd = 0;
1278 if ((cmd == 0) || (cmd == 1)) {
1280 live.sg_flags |= CBREAK;
1281 live.sg_flags |= CRMOD;
1282 live.sg_flags |= NL1;
1283 live.sg_flags &= ~ECHO;
1285 live.sg_flags |= NOFLSH;
1289 gtty(0, &saved_settings);
1292 stty(0, &saved_settings);
1299 * display_help() - help file viewer
1301 void display_help(CtdlIPC *ipc, char *name)
1308 * fmout() - Citadel text formatter and paginator
1311 int width, /* screen width to use */
1312 FILE *fpin, /* file to read from, or NULL to format given text */
1313 char *text, /* text to be formatted (when fpin is NULL */
1314 FILE *fpout, /* file to write to, or NULL to write to screen */
1315 char pagin, /* nonzero if we should use the paginator */
1316 int height, /* screen height to use */
1317 int starting_lp,/* starting value for lines_printed, -1 for global */
1318 int subst) /* nonzero if we should use hypertext mode */
1320 char *buffer = NULL; /* The current message */
1321 char *word = NULL; /* What we are about to actually print */
1322 char *e; /* Pointer to position in text */
1323 char old = 0; /* The previous character */
1324 int column = 0; /* Current column */
1325 size_t i; /* Generic counter */
1327 /* Space for a single word, which can be at most screenwidth */
1328 word = (char *)calloc(1, width);
1330 err_printf("Can't alloc memory to print message: %s!\n",
1335 /* Read the entire message body into memory */
1337 buffer = load_message_from_file(fpin);
1339 err_printf("Can't print message: %s!\n",
1348 if (starting_lp >= 0)
1349 lines_printed = starting_lp;
1351 /* Run the message body */
1353 /* Catch characters that shouldn't be there at all */
1358 /* First, are we looking at a newline? */
1361 if (*e == ' ') { /* Paragraph */
1363 fprintf(fpout, "\n");
1367 lines_printed = checkpagin(lines_printed, pagin, height);
1370 } else if (old != ' ') {/* Don't print two spaces */
1372 fprintf(fpout, " ");
1381 /* Are we looking at a nonprintable? */
1382 if ( (*e < 32) || (*e > 126) ) {
1386 /* Or are we looking at a space? */
1389 if (column >= width - 1) {
1390 /* Are we in the rightmost column? */
1392 fprintf(fpout, "\n");
1396 lines_printed = checkpagin(lines_printed, pagin, height);
1399 } else if (!(column == 0 && old == ' ')) {
1400 /* Eat only the first space on a line */
1402 fprintf(fpout, " ");
1408 /* ONLY eat the FIRST space on a line */
1414 /* Read a word, slightly messy */
1417 if (!isprint(e[i]) && !isspace(e[i]))
1424 /* We should never see these, but... slightly messy */
1425 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1428 /* Break up really long words */
1429 /* TODO: auto-hyphenation someday? */
1432 strncpy(word, e, i);
1435 /* Decide where to print the word */
1436 if (column + i >= width) {
1437 /* Wrap to the next line */
1439 fprintf(fpout, "\n");
1443 lines_printed = checkpagin(lines_printed, pagin, height);
1448 /* Print the word */
1450 fprintf(fpout, "%s", word);
1452 scr_printf("%s", word);
1455 e += i; /* Start over with the whitepsace! */
1459 if (fpin) /* We allocated this, remember? */
1462 /* Is this necessary? It makes the output kind of spacey. */
1464 fprintf(fpout, "\n");
1468 lines_printed = checkpagin(lines_printed, pagin, height);
1476 * support ANSI color if defined
1478 void color(int colornum)
1480 static int hold_color;
1481 static int current_color;
1483 if (colornum == COLOR_PUSH) {
1484 hold_color = current_color;
1488 if (colornum == COLOR_POP) {
1493 current_color = colornum;
1495 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1496 if (scr_color(colornum))
1499 /* When switching to dim white, actually output an 'original
1500 * pair' sequence -- this looks better on black-on-white
1501 * terminals. - Changed to ORIGINAL_PAIR as this actually
1502 * wound up looking horrible on black-on-white terminals, not
1503 * to mention transparent terminals.
1505 if (colornum == ORIGINAL_PAIR)
1506 printf("\033[0;39;49m");
1508 printf("\033[%d;3%d;4%dm",
1509 (colornum & 8) ? 1 : 0,
1517 void cls(int colornum)
1520 printf("\033[4%dm\033[2J\033[H\033[0m",
1521 colornum ? colornum : rc_color_use_bg);
1528 * Detect whether ANSI color is available (answerback)
1530 void send_ansi_detect(void)
1532 if (rc_ansi_color == 2) {
1539 void look_for_ansi(void)
1547 if (rc_ansi_color == 0) {
1549 } else if (rc_ansi_color == 1) {
1551 } else if (rc_ansi_color == 2) {
1553 /* otherwise, do the auto-detect */
1558 if ((now - AnsiDetect) < 2)
1567 select(1, &rfds, NULL, NULL, &tv);
1568 if (FD_ISSET(0, &rfds)) {
1569 abuf[strlen(abuf) + 1] = 0;
1570 read(0, &abuf[strlen(abuf)], 1);
1572 } while (FD_ISSET(0, &rfds));
1574 for (a = 0; a < strlen(abuf); ++a) {
1575 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1576 && (abuf[a + 2] == '?')) {
1585 * Display key options (highlight hotkeys inside angle brackets)
1587 void keyopt(char *buf) {
1591 for (i=0; i<strlen(buf); ++i) {
1594 color(BRIGHT_MAGENTA);
1608 * Present a key-menu line choice type of thing
1610 char keymenu(char *menuprompt, char *menustring) {
1616 int display_prompt = 1;
1618 choices = num_tokens(menustring, '|');
1620 if (menuprompt != NULL) do_prompt = 1;
1621 if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1624 if (display_prompt) {
1626 scr_printf("%s ", menuprompt);
1629 for (i=0; i<choices; ++i) {
1630 extract_token(buf, menustring, i, '|', sizeof buf);
1640 if ( (do_prompt) && (ch=='?') ) {
1641 scr_printf("\rOne of... ");
1643 for (i=0; i<choices; ++i) {
1644 extract_token(buf, menustring, i, '|', sizeof buf);
1653 for (i=0; i<choices; ++i) {
1654 extract_token(buf, menustring, i, '|', sizeof buf);
1655 for (c=1; c<strlen(buf); ++c) {
1656 if ( (ch == tolower(buf[c]))
1658 && (buf[c+1]=='>') ) {
1659 for (a=0; a<strlen(buf); ++a) {
1660 if ( (a!=(c-1)) && (a!=(c+1))) {