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"
69 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
73 char rc_exp_cmd[1024];
74 int rc_allow_attachments;
75 int rc_display_message_numbers;
76 int rc_force_mail_prompts;
77 int rc_remember_passwords;
80 int rc_prompt_control = 0;
81 time_t rc_idle_threshold = (time_t)900;
83 char rc_gotmail_cmd[SIZ];
86 int next_lazy_cmd = 5;
88 int lines_printed = 0; /* line count for paginator */
89 extern int screenwidth, screenheight;
91 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
93 struct citcmd *cmdlist = NULL;
96 /* these variables are local to this module */
97 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
98 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
99 time_t AnsiDetect; /* when did we send the detect code? */
100 int enable_color = 0; /* nonzero for ANSI color */
106 * If an interesting key has been pressed, return its value, otherwise 0
108 char was_a_key_pressed(void) {
118 retval = select(1, &rfds, NULL, NULL, &tv);
120 /* Careful! Disable keepalives during keyboard polling; we're probably
121 * in the middle of a data transfer from the server, in which case
122 * sending a NOOP would throw the client protocol out of sync.
124 if (FD_ISSET(0, &rfds)) {
125 set_keepalives(KA_NO);
126 the_character = inkey();
127 set_keepalives(KA_YES);
132 return(the_character);
140 * Check to see if we need to pause at the end of a screen.
141 * If we do, we have to switch to half keepalives during the pause because
142 * we are probably in the middle of a server operation and the NOOP command
143 * would confuse everything.
145 int checkpagin(int lp, unsigned int pagin, unsigned int height)
149 if (sigcaught) return(lp);
150 thekey = was_a_key_pressed();
151 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
153 if (thekey == 'n' || thekey == 'N')
155 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
156 if (sigcaught) return(lp);
158 if (!pagin) return(0);
159 if (lp>=(height-1)) {
160 set_keepalives(KA_HALF);
161 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
162 set_keepalives(KA_YES);
172 * pprintf() ... paginated version of printf()
174 void pprintf(const char *format, ...) {
176 static char buf[4096]; /* static for performance, change if needed */
179 /* If sigcaught is nonzero, a keypress has interrupted this and we
180 * should just drain output.
182 if (sigcaught) return;
184 /* Otherwise, start spewing... */
185 va_start(arg_ptr, format);
186 vsnprintf(buf, sizeof(buf), format, arg_ptr);
189 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
193 lines_printed = checkpagin(lines_printed,
194 (userflags & US_PAGINATOR),
203 * print_instant() - print instant messages if there are any
205 void print_instant(void)
214 char *listing = NULL;
215 int r; /* IPC result code */
217 if (instant_msgs == 0)
223 if (IsEmptyStr(rc_exp_cmd)) {
228 while (instant_msgs != 0) {
229 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
233 instant_msgs = extract_int(buf, 0);
234 timestamp = extract_long(buf, 1);
235 flags = extract_int(buf, 2);
236 extract_token(sender, buf, 3, '|', sizeof sender);
237 extract_token(node, buf, 4, '|', sizeof node);
238 strcpy(last_paged, sender);
240 localtime_r(×tamp, &stamp);
242 /* If the page is a Logoff Request, honor it. */
248 if (!IsEmptyStr(rc_exp_cmd)) {
249 outpipe = popen(rc_exp_cmd, "w");
250 if (outpipe != NULL) {
251 /* Header derived from flags */
254 "Please log off now, as requested ");
256 fprintf(outpipe, "Broadcast message ");
258 fprintf(outpipe, "Chat request ");
260 fprintf(outpipe, "Message ");
261 /* Timestamp. Can this be improved? */
262 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
263 fprintf(outpipe, "at 12:%02d%cm",
265 stamp.tm_hour ? 'p' : 'a');
266 else if (stamp.tm_hour > 12) /* pm */
267 fprintf(outpipe, "at %d:%02dpm",
271 fprintf(outpipe, "at %d:%02dam",
272 stamp.tm_hour, stamp.tm_min);
273 fprintf(outpipe, " from %s", sender);
274 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
275 fprintf(outpipe, " @%s", node);
276 fprintf(outpipe, ":\n%s\n", listing);
278 if (instant_msgs == 0)
283 /* fall back to built-in instant message display */
287 /* Header derived from flags */
289 scr_printf("Please log off now, as requested ");
291 scr_printf("Broadcast message ");
293 scr_printf("Chat request ");
295 scr_printf("Message ");
297 /* Timestamp. Can this be improved? */
298 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
299 scr_printf("at 12:%02d%cm", stamp.tm_min,
300 stamp.tm_hour ? 'p' : 'a');
301 else if (stamp.tm_hour > 12) /* pm */
302 scr_printf("at %d:%02dpm",
303 stamp.tm_hour - 12, stamp.tm_min);
305 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
308 scr_printf(" from %s", sender);
310 /* Remote node, if any */
311 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
312 scr_printf(" @%s", node);
316 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
319 /* when running in curses mode, the scroll bar in most
320 xterm-style programs becomes useless, so it makes sense to
321 pause after a screenful of pages if the user has been idle
322 for a while. However, this is annoying to some of the users
323 who aren't in curses mode and tend to leave their clients
324 idle. keepalives become disabled, resulting in getting booted
325 when coming back to the idle session. but they probably have
326 a working scrollback in their terminal, so disable it in this
329 if (!is_curses_enabled())
332 scr_printf("\n---\n");
339 void set_keepalives(int s)
341 keepalives_enabled = (char) s;
345 * This loop handles the "keepalive" messages sent to the server when idling.
348 static time_t idlet = 0;
349 static void really_do_keepalive(void) {
350 int r; /* IPC response code */
354 /* This may sometimes get called before we are actually connected
355 * to the server. Don't do anything if we aren't connected. -IO
357 if (!ipc_for_signal_handlers)
360 /* If full keepalives are enabled, send a NOOP to the server and
361 * wait for a response.
363 if (keepalives_enabled == KA_YES) {
364 r = CtdlIPCNoop(ipc_for_signal_handlers);
365 if (instant_msgs > 0) {
366 if (ok_to_interrupt == 1) {
367 scr_printf("\r%64s\r", "");
369 scr_printf("%s%c ", room_name,
370 room_prompt(room_flags));
376 /* If half keepalives are enabled, send a QNOP to the server (if the
377 * server supports it) and then do nothing.
379 if ( (keepalives_enabled == KA_HALF)
380 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
381 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
385 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
386 encapsulated interface; in theory there should be no need to touch these
387 globals outside of the async_ka_* functions. */
389 #ifdef THREADED_CLIENT
390 static pthread_t ka_thr_handle;
391 static int ka_thr_active = 0;
392 static int async_ka_enabled = 0;
394 static void *ka_thread(void *arg)
396 really_do_keepalive();
397 pthread_detach(ka_thr_handle);
402 /* start up a thread to handle a keepalive in the background */
403 static void async_ka_exec(void)
405 if (!ka_thr_active) {
407 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
408 perror("pthread_create");
413 #endif /* THREADED_CLIENT */
415 /* I changed this from static to not because I need to call it from
416 screen.c, either that or make something in screen.c not static.
417 Fix it how you like. Why all the staticness? stu */
419 void do_keepalive(void)
424 if ((now - idlet) < ((long) S_KEEPALIVE))
427 /* Do a space-backspace to keep telnet sessions from idling out */
428 scr_printf(" %c", 8);
431 #ifdef THREADED_CLIENT
432 if (async_ka_enabled)
436 really_do_keepalive();
440 /* Now the actual async-keepalve API that we expose to higher levels:
441 async_ka_start() and async_ka_end(). These do nothing when we don't have
442 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
444 /* wait for a background keepalive to complete. this must be done before
445 attempting any further server requests! */
446 void async_ka_end(void)
448 #ifdef THREADED_CLIENT
450 pthread_join(ka_thr_handle, NULL);
456 /* tell do_keepalive() that keepalives are asynchronous. */
457 void async_ka_start(void)
459 #ifdef THREADED_CLIENT
466 { /* get a character from the keyboard, with */
467 int a; /* the watchdog timer in effect if necessary */
477 /* This loop waits for keyboard input. If the keepalive
478 * timer expires, it sends a keepalive to the server if
479 * necessary and then waits again.
482 scr_set_windowsize(ipc_for_signal_handlers);
484 scr_set_windowsize(ipc_for_signal_handlers);
488 tv.tv_sec = S_KEEPALIVE;
491 select(1, &rfds, NULL, NULL, &tv);
492 } while (!FD_ISSET(0, &rfds));
494 /* At this point, there's input, so fetch it.
495 * (There's a hole in the bucket...)
497 a = scr_getc(SCR_BLOCK);
504 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
508 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
509 && ((a < 32) || (a > 126))) {
514 #ifndef DISABLE_CURSES
515 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
528 { /* Returns 1 for yes, 0 for no */
544 /* Returns 1 for yes, 0 for no, arg is default value */
567 /* Gets a line from the terminal */
568 /* string == Pointer to string buffer */
569 /* lim == Maximum length - if negative, no-show */
570 void ctdl_getline(char *string, int lim)
584 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
585 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
587 if ((a != 10) && (a != 8) && (strlen(string) == lim))
589 if ((a == 8) && (string[0] != 0)) {
590 string[strlen(string) - 1] = 0;
591 scr_putc(8); scr_putc(32); scr_putc(8);
594 if ((a == 23) && (string[0] != 0)) {
596 string[strlen(string) - 1] = 0;
597 scr_putc(8); scr_putc(32); scr_putc(8);
598 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
620 * strprompt() - prompt for a string, print the existing value and
621 * allow the user to press return to keep it...
623 void strprompt(char *prompt, char *str, int len)
630 scr_printf("%s ", prompt);
633 color(BRIGHT_MAGENTA);
636 scr_printf("%s", str);
639 for (i=0; !IsEmptyStr(&str[i]); ++i) {
649 ctdl_getline(buf, len);
656 * boolprompt() - prompt for a yes/no, print the existing value and
657 * allow the user to press return to keep it...
659 int boolprompt(char *prompt, int prev_val)
664 scr_printf("%s ", prompt);
667 color(BRIGHT_MAGENTA);
668 scr_printf("%s", (prev_val ? "Yes" : "No"));
672 r = (yesno_d(prev_val));
678 * intprompt() - like strprompt(), except for an integer
679 * (note that it RETURNS the new value!)
681 int intprompt(char *prompt, int ival, int imin, int imax)
689 snprintf(buf, sizeof buf, "%d", i);
690 strprompt(prompt, buf, 15);
692 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
693 if ( (!isdigit(buf[p]))
694 && ( (buf[p]!='-') || (p!=0) ) )
698 scr_printf("*** Must be no less than %d.\n", imin);
700 scr_printf("*** Must be no more than %d.\n", imax);
701 } while ((i < imin) || (i > imax));
706 * newprompt() - prompt for a string with no existing value
707 * (clears out string buffer first)
709 void newprompt(char *prompt, char *str, int len)
711 color(BRIGHT_MAGENTA);
712 scr_printf("%s", prompt);
714 ctdl_getline(str, len);
720 { /* returns a lower case value */
729 * parse the citadel.rc file
731 void load_command_set(void)
735 char editor_key[100];
737 struct citcmd *lastcmd = NULL;
743 /* first, set up some defaults for non-required variables */
745 for (i = 0; i < MAX_EDITORS; i++)
746 strcpy(editor_paths[i], "");
747 strcpy(printcmd, "");
748 strcpy(imagecmd, "");
749 strcpy(rc_username, "");
750 strcpy(rc_password, "");
753 rc_allow_attachments = 0;
754 rc_remember_passwords = 0;
755 strcpy(rc_exp_cmd, "");
756 rc_display_message_numbers = 0;
757 rc_force_mail_prompts = 0;
760 strcpy(rc_url_cmd, "");
761 strcpy(rc_gotmail_cmd, "");
763 rc_encrypt = RC_DEFAULT;
765 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
766 rc_screen = RC_DEFAULT;
768 rc_alt_semantics = 0;
770 /* now try to open the citadel.rc file */
773 if (getenv("HOME") != NULL) {
774 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
775 ccfile = fopen(buf, "r");
777 if (ccfile == NULL) {
778 ccfile = fopen(file_citadel_rc, "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 ((!IsEmptyStr(buf)) ? (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;
909 if (!strncasecmp(buf, "cmd=", 4)) {
910 strcpy(buf, &buf[4]);
912 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
914 cptr->c_cmdnum = atoi(buf);
915 for (d = strlen(buf); d >= 0; --d)
918 strcpy(buf, &buf[b + 1]);
920 cptr->c_axlevel = atoi(buf);
921 for (d = strlen(buf); d >= 0; --d)
924 strcpy(buf, &buf[b + 1]);
926 for (a = 0; a < 5; ++a)
927 cptr->c_keys[a][0] = 0;
931 buf[strlen(buf) + 1] = 0;
932 while (!IsEmptyStr(buf)) {
934 for (d = strlen(buf); d >= 0; --d)
937 strncpy(cptr->c_keys[a], buf, b);
938 cptr->c_keys[a][b] = 0;
940 strcpy(buf, &buf[b + 1]);
950 lastcmd->next = cptr;
960 * return the key associated with a command
962 char keycmd(char *cmdstr)
966 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
967 if (cmdstr[a] == '&')
968 return (tolower(cmdstr[a + 1]));
974 * Output the string from a key command without the ampersand
975 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
977 char *cmd_expand(char *strbuf, int mode)
987 while (!IsEmptyStr(ptr)){
990 /* dont echo these non mnemonic command keys */
991 int noecho = *(ptr+1) == '<' || *(ptr+1) == '>' ||
992 *(ptr+1) == '+' || *(ptr+1) == '-';
995 strcpy(ptr, ptr + 1 + noecho);
999 strcpy(buf, ptr + 2);
1005 if (!strncmp(ptr, "^r", 2)) {
1007 strcpy(ptr, room_name);
1008 strcat(exp, &buf[ptr - exp + 2]);
1010 if (!strncmp(ptr, "^c", 2)) {
1012 strcpy(ptr + 1], ptr + 2]);
1022 * Comparison function to determine if entered commands match a
1023 * command loaded from the config file.
1025 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1036 for (a = 0; a < ncomp; ++a) {
1037 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1038 || (cptr->c_axlevel > cmdax))
1046 * This function returns 1 if a given command requires a string input
1048 int requires_string(struct citcmd *cptr, int ncomp)
1053 strcpy(buf, cptr->c_keys[ncomp - 1]);
1054 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1063 * Input a command at the main prompt.
1064 * This function returns an integer command number. If the command prompts
1065 * for a string then it is placed in the supplied buffer.
1067 int getcmd(CtdlIPC *ipc, char *argbuf)
1076 struct citcmd *cptr;
1079 * Starting a new command now, so set sigcaught to 0. This variable
1080 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1081 * been interrupted by a keypress.
1085 /* Switch color support on or off if we're in user mode */
1086 if (rc_ansi_color == 3) {
1087 if (userflags & US_COLOR)
1092 /* if we're running in idiot mode, display a cute little menu */
1093 IFNEXPERT formout(ipc, "mainmenu");
1098 for (a = 0; a < 5; ++a)
1100 /* now the room prompt... */
1101 ok_to_interrupt = 1;
1102 color(BRIGHT_WHITE);
1103 scr_printf("\n%s", room_name);
1105 scr_printf("%c ", room_prompt(room_flags));
1110 ok_to_interrupt = 0;
1112 /* Handle the backspace key, but only if there's something
1113 * to backspace over...
1115 if ((ch == 8) && (cmdpos > 0)) {
1116 back(cmdspaces[cmdpos - 1] + 1);
1120 /* Spacebar invokes "lazy traversal" commands */
1121 if ((ch == 32) && (cmdpos == 0)) {
1122 this_lazy_cmd = next_lazy_cmd;
1123 if (this_lazy_cmd == 13)
1125 if (this_lazy_cmd == 5)
1127 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1128 if (cptr->c_cmdnum == this_lazy_cmd) {
1129 for (a = 0; a < 5; ++a)
1130 if (cptr->c_keys[a][0] != 0)
1131 scr_printf("%s ", cmd_expand(
1132 cptr->c_keys[a], 0));
1134 return (this_lazy_cmd);
1138 return (this_lazy_cmd);
1140 /* Otherwise, process the command */
1141 cmdbuf[cmdpos] = tolower(ch);
1143 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1144 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1146 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1147 cmdspaces[cmdpos] = strlen(
1148 cmd_expand(cptr->c_keys[cmdpos], 0));
1150 if ((cptr->c_keys[cmdpos + 1]) != 0)
1156 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1157 if (cmdmatch(cmdbuf, cptr, 5)) {
1158 /* We've found our command. */
1159 if (requires_string(cptr, cmdpos)) {
1160 ctdl_getline(argbuf, 64);
1165 /* If this command is one that changes rooms,
1166 * then the next lazy-command (space bar)
1167 * should be "read new" instead of "goto"
1169 if ((cptr->c_cmdnum == 5)
1170 || (cptr->c_cmdnum == 6)
1171 || (cptr->c_cmdnum == 47)
1172 || (cptr->c_cmdnum == 52)
1173 || (cptr->c_cmdnum == 16)
1174 || (cptr->c_cmdnum == 20))
1177 /* If this command is "read new"
1178 * then the next lazy-command (space bar)
1181 if (cptr->c_cmdnum == 13)
1184 return (cptr->c_cmdnum);
1190 pprintf("\rOne of ... \n");
1191 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1192 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1193 for (a = 0; a < 5; ++a) {
1194 keyopt(cmd_expand(cptr->c_keys[a], 1));
1202 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1204 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1205 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1206 for (a = 0; a < cmdpos; ++a) {
1208 cmd_expand(cptr->c_keys[a], 0));
1223 * set tty modes. commands are:
1225 * 01- set to Citadel mode
1226 * 2 - save current settings for later restoral
1227 * 3 - restore saved settings
1229 #ifdef HAVE_TERMIOS_H
1230 void stty_ctdl(int cmd)
1231 { /* SysV version of stty_ctdl() */
1232 struct termios live;
1233 static struct termios saved_settings;
1234 static int last_cmd = 0;
1241 if ((cmd == 0) || (cmd == 1)) {
1242 tcgetattr(0, &live);
1243 live.c_iflag = ISTRIP | IXON | IXANY;
1244 live.c_oflag = OPOST | ONLCR;
1245 live.c_lflag = ISIG | NOFLSH;
1247 live.c_cc[VINTR] = 0;
1248 live.c_cc[VQUIT] = 0;
1251 live.c_cc[VMIN] = 0;
1252 live.c_cc[VTIME] = 0;
1255 /* do we even need this stuff anymore? */
1256 /* live.c_line=0; */
1257 live.c_cc[VERASE] = 8;
1258 live.c_cc[VKILL] = 24;
1259 live.c_cc[VEOF] = 1;
1260 live.c_cc[VEOL] = 255;
1261 live.c_cc[VEOL2] = 0;
1262 live.c_cc[VSTART] = 0;
1263 tcsetattr(0, TCSADRAIN, &live);
1266 tcgetattr(0, &saved_settings);
1269 tcsetattr(0, TCSADRAIN, &saved_settings);
1274 void stty_ctdl(int cmd)
1275 { /* BSD version of stty_ctdl() */
1277 static struct sgttyb saved_settings;
1278 static int last_cmd = 0;
1285 if ((cmd == 0) || (cmd == 1)) {
1287 live.sg_flags |= CBREAK;
1288 live.sg_flags |= CRMOD;
1289 live.sg_flags |= NL1;
1290 live.sg_flags &= ~ECHO;
1292 live.sg_flags |= NOFLSH;
1296 gtty(0, &saved_settings);
1299 stty(0, &saved_settings);
1306 * display_help() - help file viewer
1308 void display_help(CtdlIPC *ipc, char *name)
1315 * fmout() - Citadel text formatter and paginator
1318 int width, /* screen width to use */
1319 FILE *fpin, /* file to read from, or NULL to format given text */
1320 char *text, /* text to be formatted (when fpin is NULL */
1321 FILE *fpout, /* file to write to, or NULL to write to screen */
1322 char pagin, /* nonzero if we should use the paginator */
1323 int height, /* screen height to use */
1324 int starting_lp,/* starting value for lines_printed, -1 for global */
1325 int subst) /* nonzero if we should use hypertext mode */
1327 char *buffer = NULL; /* The current message */
1328 char *word = NULL; /* What we are about to actually print */
1329 char *e; /* Pointer to position in text */
1330 char old = 0; /* The previous character */
1331 int column = 0; /* Current column */
1332 size_t i; /* Generic counter */
1334 /* Space for a single word, which can be at most screenwidth */
1335 word = (char *)calloc(1, width);
1337 err_printf("Can't alloc memory to print message: %s!\n",
1342 /* Read the entire message body into memory */
1344 buffer = load_message_from_file(fpin);
1346 err_printf("Can't print message: %s!\n",
1355 if (starting_lp >= 0)
1356 lines_printed = starting_lp;
1358 /* Run the message body */
1360 /* Catch characters that shouldn't be there at all */
1365 /* First, are we looking at a newline? */
1368 if (*e == ' ') { /* Paragraph */
1370 fprintf(fpout, "\n");
1374 lines_printed = checkpagin(lines_printed, pagin, height);
1377 } else if (old != ' ') {/* Don't print two spaces */
1379 fprintf(fpout, " ");
1389 /* Are we looking at a nonprintable?
1390 * (This section is now commented out because we could be displaying
1391 * a character set like UTF-8 or ISO-8859-1.)
1392 if ( (*e < 32) || (*e > 126) ) {
1397 /* Or are we looking at a space? */
1400 if (column >= width - 1) {
1401 /* Are we in the rightmost column? */
1403 fprintf(fpout, "\n");
1407 lines_printed = checkpagin(lines_printed, pagin, height);
1410 } else if (!(column == 0 && old == ' ')) {
1411 /* Eat only the first space on a line */
1413 fprintf(fpout, " ");
1419 /* ONLY eat the FIRST space on a line */
1425 /* Read a word, slightly messy */
1428 if (!isprint(e[i]) && !isspace(e[i]))
1435 /* We should never see these, but... slightly messy */
1436 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1439 /* Break up really long words */
1440 /* TODO: auto-hyphenation someday? */
1443 strncpy(word, e, i);
1446 /* Decide where to print the word */
1447 if (column + i >= width) {
1448 /* Wrap to the next line */
1450 fprintf(fpout, "\n");
1454 lines_printed = checkpagin(lines_printed, pagin, height);
1459 /* Print the word */
1461 fprintf(fpout, "%s", word);
1463 scr_printf("%s", word);
1466 e += i; /* Start over with the whitepsace! */
1470 if (fpin) /* We allocated this, remember? */
1473 /* Is this necessary? It makes the output kind of spacey. */
1475 fprintf(fpout, "\n");
1479 lines_printed = checkpagin(lines_printed, pagin, height);
1487 * support ANSI color if defined
1489 void color(int colornum)
1491 static int hold_color;
1492 static int current_color;
1494 if (colornum == COLOR_PUSH) {
1495 hold_color = current_color;
1499 if (colornum == COLOR_POP) {
1504 current_color = colornum;
1506 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1507 if (scr_color(colornum))
1510 /* When switching to dim white, actually output an 'original
1511 * pair' sequence -- this looks better on black-on-white
1512 * terminals. - Changed to ORIGINAL_PAIR as this actually
1513 * wound up looking horrible on black-on-white terminals, not
1514 * to mention transparent terminals.
1516 if (colornum == ORIGINAL_PAIR)
1517 printf("\033[0;39;49m");
1519 printf("\033[%d;3%d;4%dm",
1520 (colornum & 8) ? 1 : 0,
1528 void cls(int colornum)
1531 printf("\033[4%dm\033[2J\033[H\033[0m",
1532 colornum ? colornum : rc_color_use_bg);
1539 * Detect whether ANSI color is available (answerback)
1541 void send_ansi_detect(void)
1543 if (rc_ansi_color == 2) {
1550 void look_for_ansi(void)
1558 if (rc_ansi_color == 0) {
1560 } else if (rc_ansi_color == 1) {
1562 } else if (rc_ansi_color == 2) {
1564 /* otherwise, do the auto-detect */
1569 if ((now - AnsiDetect) < 2)
1578 select(1, &rfds, NULL, NULL, &tv);
1579 if (FD_ISSET(0, &rfds)) {
1580 abuf[strlen(abuf) + 1] = 0;
1581 read(0, &abuf[strlen(abuf)], 1);
1583 } while (FD_ISSET(0, &rfds));
1585 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1586 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1587 && (abuf[a + 2] == '?')) {
1596 * Display key options (highlight hotkeys inside angle brackets)
1598 void keyopt(char *buf) {
1602 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1604 pprintf("%c", buf[i]);
1605 color(BRIGHT_MAGENTA);
1607 if (buf[i]=='>'&& buf[i+1] != '>') {
1610 pprintf("%c", buf[i]);
1619 * Present a key-menu line choice type of thing
1621 char keymenu(char *menuprompt, char *menustring) {
1627 int display_prompt = 1;
1629 choices = num_tokens(menustring, '|');
1631 if (menuprompt != NULL) do_prompt = 1;
1632 if (menuprompt != NULL) if (IsEmptyStr(menuprompt)) do_prompt = 0;
1635 if (display_prompt) {
1637 scr_printf("%s ", menuprompt);
1640 for (i=0; i<choices; ++i) {
1641 extract_token(buf, menustring, i, '|', sizeof buf);
1651 if ( (do_prompt) && (ch=='?') ) {
1652 scr_printf("\rOne of... ");
1654 for (i=0; i<choices; ++i) {
1655 extract_token(buf, menustring, i, '|', sizeof buf);
1664 for (i=0; i<choices; ++i) {
1665 extract_token(buf, menustring, i, '|', sizeof buf);
1666 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1667 if ( (ch == tolower(buf[c]))
1669 && (buf[c+1]=='>') ) {
1670 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1671 if ( (a!=(c-1)) && (a!=(c+1))) {