2 * This file contains functions which implement parts of the
3 * text-mode user interface.
5 * Copyright (c) 1987-2009 by the citadel.org team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include <sys/types.h>
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
36 # include <sys/time.h>
48 #ifdef HAVE_SYS_SELECT_H
49 #include <sys/select.h>
52 #ifdef THREADED_CLIENT
59 #include <libcitadel.h>
61 #include "citadel_ipc.h"
64 #include "citadel_decls.h"
66 #include "routines2.h"
68 #include "client_chat.h"
69 #include "citadel_dirs.h"
83 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
87 char rc_exp_cmd[1024];
88 int rc_allow_attachments;
89 int rc_display_message_numbers;
90 int rc_force_mail_prompts;
91 int rc_remember_passwords;
94 int rc_prompt_control = 0;
95 time_t rc_idle_threshold = (time_t)900;
97 char rc_open_cmd[SIZ];
98 char rc_gotmail_cmd[SIZ];
101 int next_lazy_cmd = 5;
103 int lines_printed = 0; /* line count for paginator */
104 extern int screenwidth, screenheight;
106 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
108 struct citcmd *cmdlist = NULL;
111 /* these variables are local to this module */
112 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
113 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
114 time_t AnsiDetect; /* when did we send the detect code? */
115 int enable_color = 0; /* nonzero for ANSI color */
121 * If an interesting key has been pressed, return its value, otherwise 0
123 char was_a_key_pressed(void) {
133 retval = select(1, &rfds, NULL, NULL, &tv);
135 /* Careful! Disable keepalives during keyboard polling; we're probably
136 * in the middle of a data transfer from the server, in which case
137 * sending a NOOP would throw the client protocol out of sync.
139 if (FD_ISSET(0, &rfds)) {
140 set_keepalives(KA_NO);
141 the_character = inkey();
142 set_keepalives(KA_YES);
147 return(the_character);
155 * Check to see if we need to pause at the end of a screen.
156 * If we do, we have to switch to half keepalives during the pause because
157 * we are probably in the middle of a server operation and the NOOP command
158 * would confuse everything.
160 int checkpagin(int lp, unsigned int pagin, unsigned int height)
164 if (sigcaught) return(lp);
165 thekey = was_a_key_pressed();
166 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
168 if (thekey == 'n' || thekey == 'N')
170 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
171 if (sigcaught) return(lp);
173 if (!pagin) return(0);
174 if (lp>=(height-1)) {
175 set_keepalives(KA_HALF);
176 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
177 set_keepalives(KA_YES);
187 * pprintf() ... paginated version of printf()
189 void pprintf(const char *format, ...) {
191 static char buf[4096]; /* static for performance, change if needed */
194 /* If sigcaught is nonzero, a keypress has interrupted this and we
195 * should just drain output.
197 if (sigcaught) return;
199 /* Otherwise, start spewing... */
200 va_start(arg_ptr, format);
201 vsnprintf(buf, sizeof(buf), format, arg_ptr);
204 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
208 lines_printed = checkpagin(lines_printed,
209 (userflags & US_PAGINATOR),
218 * print_instant() - print instant messages if there are any
220 void print_instant(void)
229 char *listing = NULL;
230 int r; /* IPC result code */
232 if (instant_msgs == 0)
238 if (IsEmptyStr(rc_exp_cmd)) {
243 while (instant_msgs != 0) {
244 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
248 instant_msgs = extract_int(buf, 0);
249 timestamp = extract_long(buf, 1);
250 flags = extract_int(buf, 2);
251 extract_token(sender, buf, 3, '|', sizeof sender);
252 extract_token(node, buf, 4, '|', sizeof node);
253 strcpy(last_paged, sender);
255 localtime_r(×tamp, &stamp);
257 /* If the page is a Logoff Request, honor it. */
263 if (!IsEmptyStr(rc_exp_cmd)) {
264 outpipe = popen(rc_exp_cmd, "w");
265 if (outpipe != NULL) {
266 /* Header derived from flags */
269 "Please log off now, as requested ");
271 fprintf(outpipe, "Broadcast message ");
273 fprintf(outpipe, "Chat request ");
275 fprintf(outpipe, "Message ");
276 /* Timestamp. Can this be improved? */
277 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
278 fprintf(outpipe, "at 12:%02d%cm",
280 stamp.tm_hour ? 'p' : 'a');
281 else if (stamp.tm_hour > 12) /* pm */
282 fprintf(outpipe, "at %d:%02dpm",
286 fprintf(outpipe, "at %d:%02dam",
287 stamp.tm_hour, stamp.tm_min);
288 fprintf(outpipe, " from %s", sender);
289 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
290 fprintf(outpipe, " @%s", node);
291 fprintf(outpipe, ":\n%s\n", listing);
293 if (instant_msgs == 0)
298 /* fall back to built-in instant message display */
302 /* Header derived from flags */
304 scr_printf("Please log off now, as requested ");
306 scr_printf("Broadcast message ");
308 scr_printf("Chat request ");
310 scr_printf("Message ");
312 /* Timestamp. Can this be improved? */
313 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
314 scr_printf("at 12:%02d%cm", stamp.tm_min,
315 stamp.tm_hour ? 'p' : 'a');
316 else if (stamp.tm_hour > 12) /* pm */
317 scr_printf("at %d:%02dpm",
318 stamp.tm_hour - 12, stamp.tm_min);
320 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
323 scr_printf(" from %s", sender);
325 /* Remote node, if any */
326 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
327 scr_printf(" @%s", node);
331 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
336 scr_printf("\n---\n");
343 void set_keepalives(int s)
345 keepalives_enabled = (char) s;
349 * This loop handles the "keepalive" messages sent to the server when idling.
352 static time_t idlet = 0;
353 static void really_do_keepalive(void) {
354 int r; /* IPC response code */
358 /* This may sometimes get called before we are actually connected
359 * to the server. Don't do anything if we aren't connected. -IO
361 if (!ipc_for_signal_handlers)
364 /* If full keepalives are enabled, send a NOOP to the server and
365 * wait for a response.
367 if (keepalives_enabled == KA_YES) {
368 r = CtdlIPCNoop(ipc_for_signal_handlers);
369 if (instant_msgs > 0) {
370 if (ok_to_interrupt == 1) {
371 scr_printf("\r%64s\r", "");
373 scr_printf("%s%c ", room_name,
374 room_prompt(room_flags));
380 /* If half keepalives are enabled, send a QNOP to the server (if the
381 * server supports it) and then do nothing.
383 if ( (keepalives_enabled == KA_HALF)
384 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
385 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
389 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
390 encapsulated interface; in theory there should be no need to touch these
391 globals outside of the async_ka_* functions. */
393 #ifdef THREADED_CLIENT
394 static pthread_t ka_thr_handle;
395 static int ka_thr_active = 0;
396 static int async_ka_enabled = 0;
398 static void *ka_thread(void *arg)
400 #ifdef HAVE_BACKTRACE
401 char threadName[256];
404 sprintf(threadName, "ka_Thread n");
406 // Register for tracing
407 eCrash_RegisterThread(threadName, 0);
409 really_do_keepalive();
410 pthread_detach(ka_thr_handle);
413 #ifdef HAVE_BACKTRACE
414 eCrash_UnregisterThread();
419 /* start up a thread to handle a keepalive in the background */
420 static void async_ka_exec(void)
422 if (!ka_thr_active) {
424 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
425 perror("pthread_create");
430 #endif /* THREADED_CLIENT */
432 /* I changed this from static to not because I need to call it from
433 screen.c, either that or make something in screen.c not static.
434 Fix it how you like. Why all the staticness? stu */
436 void do_keepalive(void)
441 if ((now - idlet) < ((long) S_KEEPALIVE))
444 /* Do a space-backspace to keep telnet sessions from idling out */
445 scr_printf(" %c", 8);
448 #ifdef THREADED_CLIENT
449 if (async_ka_enabled)
453 really_do_keepalive();
457 /* Now the actual async-keepalve API that we expose to higher levels:
458 async_ka_start() and async_ka_end(). These do nothing when we don't have
459 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
461 /* wait for a background keepalive to complete. this must be done before
462 attempting any further server requests! */
463 void async_ka_end(void)
465 #ifdef THREADED_CLIENT
467 pthread_join(ka_thr_handle, NULL);
473 /* tell do_keepalive() that keepalives are asynchronous. */
474 void async_ka_start(void)
476 #ifdef THREADED_CLIENT
483 { /* get a character from the keyboard, with */
484 int a; /* the watchdog timer in effect if necessary */
494 /* This loop waits for keyboard input. If the keepalive
495 * timer expires, it sends a keepalive to the server if
496 * necessary and then waits again.
499 scr_set_windowsize(ipc_for_signal_handlers);
501 scr_set_windowsize(ipc_for_signal_handlers);
505 tv.tv_sec = S_KEEPALIVE;
508 select(1, &rfds, NULL, NULL, &tv);
509 } while (!FD_ISSET(0, &rfds));
511 /* At this point, there's input, so fetch it.
512 * (There's a hole in the bucket...)
514 a = scr_getc(SCR_BLOCK);
521 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
525 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
526 && ((a < 32) || (a > 126))) {
536 { /* Returns 1 for yes, 0 for no */
552 /* Returns 1 for yes, 0 for no, arg is default value */
575 /* Gets a line from the terminal */
576 /* string == Pointer to string buffer */
577 /* lim == Maximum length - if negative, no-show */
578 void ctdl_getline(char *string, int lim)
592 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
593 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
595 if ((a != 10) && (a != 8) && (strlen(string) == lim))
597 if ((a == 8) && (string[0] != 0)) {
598 string[strlen(string) - 1] = 0;
599 scr_putc(8); scr_putc(32); scr_putc(8);
602 if ((a == 23) && (string[0] != 0)) {
604 string[strlen(string) - 1] = 0;
605 scr_putc(8); scr_putc(32); scr_putc(8);
606 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
628 * strprompt() - prompt for a string, print the existing value and
629 * allow the user to press return to keep it...
631 void strprompt(char *prompt, char *str, int len)
638 scr_printf("%s ", prompt);
641 color(BRIGHT_MAGENTA);
644 scr_printf("%s", str);
647 for (i=0; !IsEmptyStr(&str[i]); ++i) {
657 ctdl_getline(buf, len);
664 * boolprompt() - prompt for a yes/no, print the existing value and
665 * allow the user to press return to keep it...
667 int boolprompt(char *prompt, int prev_val)
672 scr_printf("%s ", prompt);
675 color(BRIGHT_MAGENTA);
676 scr_printf("%s", (prev_val ? "Yes" : "No"));
680 r = (yesno_d(prev_val));
686 * intprompt() - like strprompt(), except for an integer
687 * (note that it RETURNS the new value!)
689 int intprompt(char *prompt, int ival, int imin, int imax)
697 snprintf(buf, sizeof buf, "%d", i);
698 strprompt(prompt, buf, 15);
700 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
701 if ( (!isdigit(buf[p]))
702 && ( (buf[p]!='-') || (p!=0) ) )
706 scr_printf("*** Must be no less than %d.\n", imin);
708 scr_printf("*** Must be no more than %d.\n", imax);
709 } while ((i < imin) || (i > imax));
714 * newprompt() - prompt for a string with no existing value
715 * (clears out string buffer first)
717 void newprompt(char *prompt, char *str, int len)
719 color(BRIGHT_MAGENTA);
720 scr_printf("%s", prompt);
722 ctdl_getline(str, len);
728 { /* returns a lower case value */
737 * parse the citadel.rc file
739 void load_command_set(void)
743 char editor_key[100];
745 struct citcmd *lastcmd = NULL;
751 /* first, set up some defaults for non-required variables */
753 for (i = 0; i < MAX_EDITORS; i++)
754 strcpy(editor_paths[i], "");
755 strcpy(printcmd, "");
756 strcpy(imagecmd, "");
757 strcpy(rc_username, "");
758 strcpy(rc_password, "");
761 rc_allow_attachments = 0;
762 rc_remember_passwords = 0;
763 strcpy(rc_exp_cmd, "");
764 rc_display_message_numbers = 0;
765 rc_force_mail_prompts = 0;
768 strcpy(rc_url_cmd, "");
769 strcpy(rc_open_cmd, "");
770 strcpy(rc_gotmail_cmd, "");
772 rc_encrypt = RC_DEFAULT;
774 rc_alt_semantics = 0;
776 /* now try to open the citadel.rc file */
779 if (getenv("HOME") != NULL) {
780 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
781 ccfile = fopen(buf, "r");
783 if (ccfile == NULL) {
784 ccfile = fopen(file_citadel_rc, "r");
786 if (ccfile == NULL) {
787 ccfile = fopen("/etc/citadel.rc", "r");
789 if (ccfile == NULL) {
790 ccfile = fopen("./citadel.rc", "r");
792 if (ccfile == NULL) {
793 perror("commands: cannot open citadel.rc");
796 while (fgets(buf, sizeof buf, ccfile) != NULL) {
797 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
798 buf[strlen(buf) - 1] = 0;
800 if (!strncasecmp(buf, "encrypt=", 8)) {
801 if (!strcasecmp(&buf[8], "yes")) {
805 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
810 else if (!strcasecmp(&buf[8], "no")) {
813 else if (!strcasecmp(&buf[8], "default")) {
814 rc_encrypt = RC_DEFAULT;
819 if (!strncasecmp(buf, "editor=", 7))
820 strcpy(editor_paths[0], &buf[7]);
822 for (i = 0; i < MAX_EDITORS; i++)
824 sprintf(editor_key, "editor%d=", i);
825 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
826 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
829 if (!strncasecmp(buf, "printcmd=", 9))
830 strcpy(printcmd, &buf[9]);
832 if (!strncasecmp(buf, "imagecmd=", 9))
833 strcpy(imagecmd, &buf[9]);
835 if (!strncasecmp(buf, "expcmd=", 7))
836 strcpy(rc_exp_cmd, &buf[7]);
838 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
839 have_xterm = (char) atoi(&buf[24]);
841 if (!strncasecmp(buf, "use_floors=", 11)) {
842 if (!strcasecmp(&buf[11], "yes"))
843 rc_floor_mode = RC_YES;
844 if (!strcasecmp(&buf[11], "no"))
845 rc_floor_mode = RC_NO;
846 if (!strcasecmp(&buf[11], "default"))
847 rc_floor_mode = RC_DEFAULT;
849 if (!strncasecmp(buf, "beep=", 5)) {
850 rc_exp_beep = atoi(&buf[5]);
852 if (!strncasecmp(buf, "allow_attachments=", 18)) {
853 rc_allow_attachments = atoi(&buf[18]);
855 if (!strncasecmp(buf, "idle_threshold=", 15)) {
856 rc_idle_threshold = atol(&buf[15]);
858 if (!strncasecmp(buf, "remember_passwords=", 19)) {
859 rc_remember_passwords = atoi(&buf[19]);
861 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
862 rc_display_message_numbers = atoi(&buf[24]);
864 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
865 rc_force_mail_prompts = atoi(&buf[19]);
867 if (!strncasecmp(buf, "ansi_color=", 11)) {
868 if (!strncasecmp(&buf[11], "on", 2))
870 if (!strncasecmp(&buf[11], "auto", 4))
871 rc_ansi_color = 2; /* autodetect */
872 if (!strncasecmp(&buf[11], "user", 4))
873 rc_ansi_color = 3; /* user config */
875 if (!strncasecmp(buf, "use_background=", 15)) {
876 if (!strncasecmp(&buf[15], "on", 2))
879 if (!strncasecmp(buf, "prompt_control=", 15)) {
880 if (!strncasecmp(&buf[15], "on", 2))
881 rc_prompt_control = 1;
882 if (!strncasecmp(&buf[15], "user", 4))
883 rc_prompt_control = 3; /* user config */
885 if (!strncasecmp(buf, "username=", 9))
886 strcpy(rc_username, &buf[9]);
888 if (!strncasecmp(buf, "password=", 9))
889 strcpy(rc_password, &buf[9]);
891 if (!strncasecmp(buf, "urlcmd=", 7))
892 strcpy(rc_url_cmd, &buf[7]);
894 if (!strncasecmp(buf, "opencmd=", 7))
895 strcpy(rc_open_cmd, &buf[8]);
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)
985 for (a = 0; exp[a]; ++a) {
986 if (strbuf[a] == '&') {
988 /* dont echo these non mnemonic command keys */
989 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
992 strcpy(&exp[a], &exp[a + 1 + noecho]);
996 strcpy(buf, &exp[a + 2]);
1002 if (!strncmp(&exp[a], "^r", 2)) {
1004 strcpy(&exp[a], room_name);
1005 strcat(exp, &buf[a + 2]);
1007 if (!strncmp(&exp[a], "^c", 2)) {
1009 strcpy(&exp[a + 1], &exp[a + 2]);
1019 * Comparison function to determine if entered commands match a
1020 * command loaded from the config file.
1022 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1033 for (a = 0; a < ncomp; ++a) {
1034 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1035 || (cptr->c_axlevel > cmdax))
1043 * This function returns 1 if a given command requires a string input
1045 int requires_string(struct citcmd *cptr, int ncomp)
1050 strcpy(buf, cptr->c_keys[ncomp - 1]);
1051 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1060 * Input a command at the main prompt.
1061 * This function returns an integer command number. If the command prompts
1062 * for a string then it is placed in the supplied buffer.
1064 int getcmd(CtdlIPC *ipc, char *argbuf)
1073 struct citcmd *cptr;
1076 * Starting a new command now, so set sigcaught to 0. This variable
1077 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1078 * been interrupted by a keypress.
1082 /* Switch color support on or off if we're in user mode */
1083 if (rc_ansi_color == 3) {
1084 if (userflags & US_COLOR)
1089 /* if we're running in idiot mode, display a cute little menu */
1090 IFNEXPERT formout(ipc, "mainmenu");
1095 for (a = 0; a < 5; ++a)
1097 /* now the room prompt... */
1098 ok_to_interrupt = 1;
1099 color(BRIGHT_WHITE);
1100 scr_printf("\n%s", room_name);
1102 scr_printf("%c ", room_prompt(room_flags));
1107 ok_to_interrupt = 0;
1109 /* Handle the backspace key, but only if there's something
1110 * to backspace over...
1112 if ((ch == 8) && (cmdpos > 0)) {
1113 back(cmdspaces[cmdpos - 1] + 1);
1117 /* Spacebar invokes "lazy traversal" commands */
1118 if ((ch == 32) && (cmdpos == 0)) {
1119 this_lazy_cmd = next_lazy_cmd;
1120 if (this_lazy_cmd == 13)
1122 if (this_lazy_cmd == 5)
1124 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1125 if (cptr->c_cmdnum == this_lazy_cmd) {
1126 for (a = 0; a < 5; ++a)
1127 if (cptr->c_keys[a][0] != 0)
1128 scr_printf("%s ", cmd_expand(
1129 cptr->c_keys[a], 0));
1131 return (this_lazy_cmd);
1135 return (this_lazy_cmd);
1137 /* Otherwise, process the command */
1138 cmdbuf[cmdpos] = tolower(ch);
1140 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1141 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1143 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1144 cmdspaces[cmdpos] = strlen(
1145 cmd_expand(cptr->c_keys[cmdpos], 0));
1147 if ((cptr->c_keys[cmdpos + 1]) != 0)
1153 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1154 if (cmdmatch(cmdbuf, cptr, 5)) {
1155 /* We've found our command. */
1156 if (requires_string(cptr, cmdpos)) {
1157 ctdl_getline(argbuf, 64);
1162 /* If this command is one that changes rooms,
1163 * then the next lazy-command (space bar)
1164 * should be "read new" instead of "goto"
1166 if ((cptr->c_cmdnum == 5)
1167 || (cptr->c_cmdnum == 6)
1168 || (cptr->c_cmdnum == 47)
1169 || (cptr->c_cmdnum == 52)
1170 || (cptr->c_cmdnum == 16)
1171 || (cptr->c_cmdnum == 20))
1174 /* If this command is "read new"
1175 * then the next lazy-command (space bar)
1178 if (cptr->c_cmdnum == 13)
1181 return (cptr->c_cmdnum);
1187 pprintf("\rOne of ... \n");
1188 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1189 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1190 for (a = 0; a < 5; ++a) {
1191 keyopt(cmd_expand(cptr->c_keys[a], 1));
1199 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1201 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1202 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1203 for (a = 0; a < cmdpos; ++a) {
1205 cmd_expand(cptr->c_keys[a], 0));
1220 * set tty modes. commands are:
1222 * 01- set to Citadel mode
1223 * 2 - save current settings for later restoral
1224 * 3 - restore saved settings
1226 #ifdef HAVE_TERMIOS_H
1227 void stty_ctdl(int cmd)
1228 { /* SysV version of stty_ctdl() */
1229 struct termios live;
1230 static struct termios saved_settings;
1231 static int last_cmd = 0;
1238 if ((cmd == 0) || (cmd == 1)) {
1239 tcgetattr(0, &live);
1240 live.c_iflag = ISTRIP | IXON | IXANY;
1241 live.c_oflag = OPOST | ONLCR;
1242 live.c_lflag = ISIG | NOFLSH;
1244 live.c_cc[VINTR] = 0;
1245 live.c_cc[VQUIT] = 0;
1248 live.c_cc[VMIN] = 0;
1249 live.c_cc[VTIME] = 0;
1252 /* do we even need this stuff anymore? */
1253 /* live.c_line=0; */
1254 live.c_cc[VERASE] = 8;
1255 live.c_cc[VKILL] = 24;
1256 live.c_cc[VEOF] = 1;
1257 live.c_cc[VEOL] = 255;
1258 live.c_cc[VEOL2] = 0;
1259 live.c_cc[VSTART] = 0;
1260 tcsetattr(0, TCSADRAIN, &live);
1263 tcgetattr(0, &saved_settings);
1266 tcsetattr(0, TCSADRAIN, &saved_settings);
1271 void stty_ctdl(int cmd)
1272 { /* BSD version of stty_ctdl() */
1274 static struct sgttyb saved_settings;
1275 static int last_cmd = 0;
1282 if ((cmd == 0) || (cmd == 1)) {
1284 live.sg_flags |= CBREAK;
1285 live.sg_flags |= CRMOD;
1286 live.sg_flags |= NL1;
1287 live.sg_flags &= ~ECHO;
1289 live.sg_flags |= NOFLSH;
1293 gtty(0, &saved_settings);
1296 stty(0, &saved_settings);
1303 * display_help() - help file viewer
1305 void display_help(CtdlIPC *ipc, char *name)
1312 * fmout() - Citadel text formatter and paginator
1315 int width, /* screen width to use */
1316 FILE *fpin, /* file to read from, or NULL to format given text */
1317 char *text, /* text to be formatted (when fpin is NULL */
1318 FILE *fpout, /* file to write to, or NULL to write to screen */
1319 char pagin, /* nonzero if we should use the paginator */
1320 int height, /* screen height to use */
1321 int starting_lp,/* starting value for lines_printed, -1 for global */
1322 int subst) /* nonzero if we should use hypertext mode */
1324 char *buffer = NULL; /* The current message */
1325 char *word = NULL; /* What we are about to actually print */
1326 char *e; /* Pointer to position in text */
1327 char old = 0; /* The previous character */
1328 int column = 0; /* Current column */
1329 size_t i; /* Generic counter */
1331 /* Space for a single word, which can be at most screenwidth */
1332 word = (char *)calloc(1, width);
1334 scr_printf("Can't alloc memory to print message: %s!\n",
1339 /* Read the entire message body into memory */
1341 buffer = load_message_from_file(fpin);
1343 scr_printf("Can't print message: %s!\n",
1352 if (starting_lp >= 0)
1353 lines_printed = starting_lp;
1355 /* Run the message body */
1357 /* Catch characters that shouldn't be there at all */
1362 /* First, are we looking at a newline? */
1365 if (*e == ' ') { /* Paragraph */
1367 fprintf(fpout, "\n");
1371 lines_printed = checkpagin(lines_printed, pagin, height);
1374 } else if (old != ' ') {/* Don't print two spaces */
1376 fprintf(fpout, " ");
1386 /* Are we looking at a nonprintable?
1387 * (This section is now commented out because we could be displaying
1388 * a character set like UTF-8 or ISO-8859-1.)
1389 if ( (*e < 32) || (*e > 126) ) {
1394 /* Or are we looking at a space? */
1397 if (column >= width - 1) {
1398 /* Are we in the rightmost column? */
1400 fprintf(fpout, "\n");
1404 lines_printed = checkpagin(lines_printed, pagin, height);
1407 } else if (!(column == 0 && old == ' ')) {
1408 /* Eat only the first space on a line */
1410 fprintf(fpout, " ");
1416 /* ONLY eat the FIRST space on a line */
1422 /* Read a word, slightly messy */
1425 if (!isprint(e[i]) && !isspace(e[i]))
1432 /* We should never see these, but... slightly messy */
1433 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1436 /* Break up really long words */
1437 /* TODO: auto-hyphenation someday? */
1440 strncpy(word, e, i);
1443 /* Decide where to print the word */
1444 if (column + i >= width) {
1445 /* Wrap to the next line */
1447 fprintf(fpout, "\n");
1451 lines_printed = checkpagin(lines_printed, pagin, height);
1456 /* Print the word */
1458 fprintf(fpout, "%s", word);
1460 scr_printf("%s", word);
1463 e += i; /* Start over with the whitepsace! */
1467 if (fpin) /* We allocated this, remember? */
1470 /* Is this necessary? It makes the output kind of spacey. */
1472 fprintf(fpout, "\n");
1476 lines_printed = checkpagin(lines_printed, pagin, height);
1484 * support ANSI color if defined
1486 void color(int colornum)
1488 static int hold_color;
1489 static int current_color;
1491 if (colornum == COLOR_PUSH) {
1492 hold_color = current_color;
1496 if (colornum == COLOR_POP) {
1501 current_color = colornum;
1503 /* When switching to dim white, actually output an 'original
1504 * pair' sequence -- this looks better on black-on-white
1505 * terminals. - Changed to ORIGINAL_PAIR as this actually
1506 * wound up looking horrible on black-on-white terminals, not
1507 * to mention transparent terminals.
1509 if (colornum == ORIGINAL_PAIR)
1510 printf("\033[0;39;49m");
1512 printf("\033[%d;3%d;4%dm",
1513 (colornum & 8) ? 1 : 0,
1521 void cls(int colornum)
1524 printf("\033[4%dm\033[2J\033[H\033[0m",
1525 colornum ? colornum : rc_color_use_bg);
1532 * Detect whether ANSI color is available (answerback)
1534 void send_ansi_detect(void)
1536 if (rc_ansi_color == 2) {
1543 void look_for_ansi(void)
1551 if (rc_ansi_color == 0) {
1553 } else if (rc_ansi_color == 1) {
1555 } else if (rc_ansi_color == 2) {
1557 /* otherwise, do the auto-detect */
1562 if ((now - AnsiDetect) < 2)
1571 select(1, &rfds, NULL, NULL, &tv);
1572 if (FD_ISSET(0, &rfds)) {
1573 abuf[strlen(abuf) + 1] = 0;
1574 rv = read(0, &abuf[strlen(abuf)], 1);
1576 } while (FD_ISSET(0, &rfds));
1578 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1579 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1580 && (abuf[a + 2] == '?')) {
1589 * Display key options (highlight hotkeys inside angle brackets)
1591 void keyopt(char *buf) {
1595 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1597 pprintf("%c", buf[i]);
1598 color(BRIGHT_MAGENTA);
1600 if (buf[i]=='>'&& buf[i+1] != '>') {
1603 pprintf("%c", buf[i]);
1612 * Present a key-menu line choice type of thing
1614 char keymenu(char *menuprompt, char *menustring) {
1620 int display_prompt = 1;
1622 choices = num_tokens(menustring, '|');
1624 if (menuprompt != NULL) do_prompt = 1;
1625 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1628 if (display_prompt) {
1630 scr_printf("%s ", menuprompt);
1633 for (i=0; i<choices; ++i) {
1634 extract_token(buf, menustring, i, '|', sizeof buf);
1644 if ( (do_prompt) && (ch=='?') ) {
1645 scr_printf("\rOne of... ");
1647 for (i=0; i<choices; ++i) {
1648 extract_token(buf, menustring, i, '|', sizeof buf);
1657 for (i=0; i<choices; ++i) {
1658 extract_token(buf, menustring, i, '|', sizeof buf);
1659 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1660 if ( (ch == tolower(buf[c]))
1662 && (buf[c+1]=='>') ) {
1663 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1664 if ( (a!=(c-1)) && (a!=(c+1))) {