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))) {
531 #ifndef DISABLE_CURSES
532 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
545 { /* Returns 1 for yes, 0 for no */
561 /* Returns 1 for yes, 0 for no, arg is default value */
584 /* Gets a line from the terminal */
585 /* string == Pointer to string buffer */
586 /* lim == Maximum length - if negative, no-show */
587 void ctdl_getline(char *string, int lim)
601 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
602 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
604 if ((a != 10) && (a != 8) && (strlen(string) == lim))
606 if ((a == 8) && (string[0] != 0)) {
607 string[strlen(string) - 1] = 0;
608 scr_putc(8); scr_putc(32); scr_putc(8);
611 if ((a == 23) && (string[0] != 0)) {
613 string[strlen(string) - 1] = 0;
614 scr_putc(8); scr_putc(32); scr_putc(8);
615 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
637 * strprompt() - prompt for a string, print the existing value and
638 * allow the user to press return to keep it...
640 void strprompt(char *prompt, char *str, int len)
647 scr_printf("%s ", prompt);
650 color(BRIGHT_MAGENTA);
653 scr_printf("%s", str);
656 for (i=0; !IsEmptyStr(&str[i]); ++i) {
666 ctdl_getline(buf, len);
673 * boolprompt() - prompt for a yes/no, print the existing value and
674 * allow the user to press return to keep it...
676 int boolprompt(char *prompt, int prev_val)
681 scr_printf("%s ", prompt);
684 color(BRIGHT_MAGENTA);
685 scr_printf("%s", (prev_val ? "Yes" : "No"));
689 r = (yesno_d(prev_val));
695 * intprompt() - like strprompt(), except for an integer
696 * (note that it RETURNS the new value!)
698 int intprompt(char *prompt, int ival, int imin, int imax)
706 snprintf(buf, sizeof buf, "%d", i);
707 strprompt(prompt, buf, 15);
709 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
710 if ( (!isdigit(buf[p]))
711 && ( (buf[p]!='-') || (p!=0) ) )
715 scr_printf("*** Must be no less than %d.\n", imin);
717 scr_printf("*** Must be no more than %d.\n", imax);
718 } while ((i < imin) || (i > imax));
723 * newprompt() - prompt for a string with no existing value
724 * (clears out string buffer first)
726 void newprompt(char *prompt, char *str, int len)
728 color(BRIGHT_MAGENTA);
729 scr_printf("%s", prompt);
731 ctdl_getline(str, len);
737 { /* returns a lower case value */
746 * parse the citadel.rc file
748 void load_command_set(void)
752 char editor_key[100];
754 struct citcmd *lastcmd = NULL;
760 /* first, set up some defaults for non-required variables */
762 for (i = 0; i < MAX_EDITORS; i++)
763 strcpy(editor_paths[i], "");
764 strcpy(printcmd, "");
765 strcpy(imagecmd, "");
766 strcpy(rc_username, "");
767 strcpy(rc_password, "");
770 rc_allow_attachments = 0;
771 rc_remember_passwords = 0;
772 strcpy(rc_exp_cmd, "");
773 rc_display_message_numbers = 0;
774 rc_force_mail_prompts = 0;
777 strcpy(rc_url_cmd, "");
778 strcpy(rc_open_cmd, "");
779 strcpy(rc_gotmail_cmd, "");
781 rc_encrypt = RC_DEFAULT;
783 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
784 rc_screen = RC_DEFAULT;
786 rc_alt_semantics = 0;
788 /* now try to open the citadel.rc file */
791 if (getenv("HOME") != NULL) {
792 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
793 ccfile = fopen(buf, "r");
795 if (ccfile == NULL) {
796 ccfile = fopen(file_citadel_rc, "r");
798 if (ccfile == NULL) {
799 ccfile = fopen("/etc/citadel.rc", "r");
801 if (ccfile == NULL) {
802 ccfile = fopen("./citadel.rc", "r");
804 if (ccfile == NULL) {
805 perror("commands: cannot open citadel.rc");
808 while (fgets(buf, sizeof buf, ccfile) != NULL) {
809 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
810 buf[strlen(buf) - 1] = 0;
812 if (!strncasecmp(buf, "encrypt=", 8)) {
813 if (!strcasecmp(&buf[8], "yes")) {
817 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
822 else if (!strcasecmp(&buf[8], "no")) {
825 else if (!strcasecmp(&buf[8], "default")) {
826 rc_encrypt = RC_DEFAULT;
831 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
832 if (!strncasecmp(buf, "fullscreen=", 11)) {
833 if (!strcasecmp(&buf[11], "yes"))
835 else if (!strcasecmp(&buf[11], "no"))
840 if (!strncasecmp(buf, "editor=", 7))
841 strcpy(editor_paths[0], &buf[7]);
843 for (i = 0; i < MAX_EDITORS; i++)
845 sprintf(editor_key, "editor%d=", i);
846 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
847 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
850 if (!strncasecmp(buf, "printcmd=", 9))
851 strcpy(printcmd, &buf[9]);
853 if (!strncasecmp(buf, "imagecmd=", 9))
854 strcpy(imagecmd, &buf[9]);
856 if (!strncasecmp(buf, "expcmd=", 7))
857 strcpy(rc_exp_cmd, &buf[7]);
859 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
860 have_xterm = (char) atoi(&buf[24]);
862 if (!strncasecmp(buf, "use_floors=", 11)) {
863 if (!strcasecmp(&buf[11], "yes"))
864 rc_floor_mode = RC_YES;
865 if (!strcasecmp(&buf[11], "no"))
866 rc_floor_mode = RC_NO;
867 if (!strcasecmp(&buf[11], "default"))
868 rc_floor_mode = RC_DEFAULT;
870 if (!strncasecmp(buf, "beep=", 5)) {
871 rc_exp_beep = atoi(&buf[5]);
873 if (!strncasecmp(buf, "allow_attachments=", 18)) {
874 rc_allow_attachments = atoi(&buf[18]);
876 if (!strncasecmp(buf, "idle_threshold=", 15)) {
877 rc_idle_threshold = atol(&buf[15]);
879 if (!strncasecmp(buf, "remember_passwords=", 19)) {
880 rc_remember_passwords = atoi(&buf[19]);
882 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
883 rc_display_message_numbers = atoi(&buf[24]);
885 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
886 rc_force_mail_prompts = atoi(&buf[19]);
888 if (!strncasecmp(buf, "ansi_color=", 11)) {
889 if (!strncasecmp(&buf[11], "on", 2))
891 if (!strncasecmp(&buf[11], "auto", 4))
892 rc_ansi_color = 2; /* autodetect */
893 if (!strncasecmp(&buf[11], "user", 4))
894 rc_ansi_color = 3; /* user config */
896 if (!strncasecmp(buf, "use_background=", 15)) {
897 if (!strncasecmp(&buf[15], "on", 2))
900 if (!strncasecmp(buf, "prompt_control=", 15)) {
901 if (!strncasecmp(&buf[15], "on", 2))
902 rc_prompt_control = 1;
903 if (!strncasecmp(&buf[15], "user", 4))
904 rc_prompt_control = 3; /* user config */
906 if (!strncasecmp(buf, "username=", 9))
907 strcpy(rc_username, &buf[9]);
909 if (!strncasecmp(buf, "password=", 9))
910 strcpy(rc_password, &buf[9]);
912 if (!strncasecmp(buf, "urlcmd=", 7))
913 strcpy(rc_url_cmd, &buf[7]);
915 if (!strncasecmp(buf, "opencmd=", 7))
916 strcpy(rc_open_cmd, &buf[8]);
918 if (!strncasecmp(buf, "gotmailcmd=", 11))
919 strcpy(rc_gotmail_cmd, &buf[11]);
921 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
922 if (!strncasecmp(&buf[20], "yes", 3)) {
923 rc_alt_semantics = 1;
926 rc_alt_semantics = 0;
930 if (!strncasecmp(buf, "cmd=", 4)) {
931 strcpy(buf, &buf[4]);
933 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
935 cptr->c_cmdnum = atoi(buf);
936 for (d = strlen(buf); d >= 0; --d)
939 strcpy(buf, &buf[b + 1]);
941 cptr->c_axlevel = atoi(buf);
942 for (d = strlen(buf); d >= 0; --d)
945 strcpy(buf, &buf[b + 1]);
947 for (a = 0; a < 5; ++a)
948 cptr->c_keys[a][0] = 0;
952 buf[strlen(buf) + 1] = 0;
953 while (!IsEmptyStr(buf)) {
955 for (d = strlen(buf); d >= 0; --d)
958 strncpy(cptr->c_keys[a], buf, b);
959 cptr->c_keys[a][b] = 0;
961 strcpy(buf, &buf[b + 1]);
971 lastcmd->next = cptr;
981 * return the key associated with a command
983 char keycmd(char *cmdstr)
987 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
988 if (cmdstr[a] == '&')
989 return (tolower(cmdstr[a + 1]));
995 * Output the string from a key command without the ampersand
996 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
998 char *cmd_expand(char *strbuf, int mode)
1001 static char exp[64];
1004 strcpy(exp, strbuf);
1006 for (a = 0; exp[a]; ++a) {
1007 if (strbuf[a] == '&') {
1009 /* dont echo these non mnemonic command keys */
1010 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1013 strcpy(&exp[a], &exp[a + 1 + noecho]);
1017 strcpy(buf, &exp[a + 2]);
1023 if (!strncmp(&exp[a], "^r", 2)) {
1025 strcpy(&exp[a], room_name);
1026 strcat(exp, &buf[a + 2]);
1028 if (!strncmp(&exp[a], "^c", 2)) {
1030 strcpy(&exp[a + 1], &exp[a + 2]);
1040 * Comparison function to determine if entered commands match a
1041 * command loaded from the config file.
1043 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1054 for (a = 0; a < ncomp; ++a) {
1055 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1056 || (cptr->c_axlevel > cmdax))
1064 * This function returns 1 if a given command requires a string input
1066 int requires_string(struct citcmd *cptr, int ncomp)
1071 strcpy(buf, cptr->c_keys[ncomp - 1]);
1072 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1081 * Input a command at the main prompt.
1082 * This function returns an integer command number. If the command prompts
1083 * for a string then it is placed in the supplied buffer.
1085 int getcmd(CtdlIPC *ipc, char *argbuf)
1094 struct citcmd *cptr;
1097 * Starting a new command now, so set sigcaught to 0. This variable
1098 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1099 * been interrupted by a keypress.
1103 /* Switch color support on or off if we're in user mode */
1104 if (rc_ansi_color == 3) {
1105 if (userflags & US_COLOR)
1110 /* if we're running in idiot mode, display a cute little menu */
1111 IFNEXPERT formout(ipc, "mainmenu");
1116 for (a = 0; a < 5; ++a)
1118 /* now the room prompt... */
1119 ok_to_interrupt = 1;
1120 color(BRIGHT_WHITE);
1121 scr_printf("\n%s", room_name);
1123 scr_printf("%c ", room_prompt(room_flags));
1128 ok_to_interrupt = 0;
1130 /* Handle the backspace key, but only if there's something
1131 * to backspace over...
1133 if ((ch == 8) && (cmdpos > 0)) {
1134 back(cmdspaces[cmdpos - 1] + 1);
1138 /* Spacebar invokes "lazy traversal" commands */
1139 if ((ch == 32) && (cmdpos == 0)) {
1140 this_lazy_cmd = next_lazy_cmd;
1141 if (this_lazy_cmd == 13)
1143 if (this_lazy_cmd == 5)
1145 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1146 if (cptr->c_cmdnum == this_lazy_cmd) {
1147 for (a = 0; a < 5; ++a)
1148 if (cptr->c_keys[a][0] != 0)
1149 scr_printf("%s ", cmd_expand(
1150 cptr->c_keys[a], 0));
1152 return (this_lazy_cmd);
1156 return (this_lazy_cmd);
1158 /* Otherwise, process the command */
1159 cmdbuf[cmdpos] = tolower(ch);
1161 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1162 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1164 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1165 cmdspaces[cmdpos] = strlen(
1166 cmd_expand(cptr->c_keys[cmdpos], 0));
1168 if ((cptr->c_keys[cmdpos + 1]) != 0)
1174 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1175 if (cmdmatch(cmdbuf, cptr, 5)) {
1176 /* We've found our command. */
1177 if (requires_string(cptr, cmdpos)) {
1178 ctdl_getline(argbuf, 64);
1183 /* If this command is one that changes rooms,
1184 * then the next lazy-command (space bar)
1185 * should be "read new" instead of "goto"
1187 if ((cptr->c_cmdnum == 5)
1188 || (cptr->c_cmdnum == 6)
1189 || (cptr->c_cmdnum == 47)
1190 || (cptr->c_cmdnum == 52)
1191 || (cptr->c_cmdnum == 16)
1192 || (cptr->c_cmdnum == 20))
1195 /* If this command is "read new"
1196 * then the next lazy-command (space bar)
1199 if (cptr->c_cmdnum == 13)
1202 return (cptr->c_cmdnum);
1208 pprintf("\rOne of ... \n");
1209 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1210 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1211 for (a = 0; a < 5; ++a) {
1212 keyopt(cmd_expand(cptr->c_keys[a], 1));
1220 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1222 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1223 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1224 for (a = 0; a < cmdpos; ++a) {
1226 cmd_expand(cptr->c_keys[a], 0));
1241 * set tty modes. commands are:
1243 * 01- set to Citadel mode
1244 * 2 - save current settings for later restoral
1245 * 3 - restore saved settings
1247 #ifdef HAVE_TERMIOS_H
1248 void stty_ctdl(int cmd)
1249 { /* SysV version of stty_ctdl() */
1250 struct termios live;
1251 static struct termios saved_settings;
1252 static int last_cmd = 0;
1259 if ((cmd == 0) || (cmd == 1)) {
1260 tcgetattr(0, &live);
1261 live.c_iflag = ISTRIP | IXON | IXANY;
1262 live.c_oflag = OPOST | ONLCR;
1263 live.c_lflag = ISIG | NOFLSH;
1265 live.c_cc[VINTR] = 0;
1266 live.c_cc[VQUIT] = 0;
1269 live.c_cc[VMIN] = 0;
1270 live.c_cc[VTIME] = 0;
1273 /* do we even need this stuff anymore? */
1274 /* live.c_line=0; */
1275 live.c_cc[VERASE] = 8;
1276 live.c_cc[VKILL] = 24;
1277 live.c_cc[VEOF] = 1;
1278 live.c_cc[VEOL] = 255;
1279 live.c_cc[VEOL2] = 0;
1280 live.c_cc[VSTART] = 0;
1281 tcsetattr(0, TCSADRAIN, &live);
1284 tcgetattr(0, &saved_settings);
1287 tcsetattr(0, TCSADRAIN, &saved_settings);
1292 void stty_ctdl(int cmd)
1293 { /* BSD version of stty_ctdl() */
1295 static struct sgttyb saved_settings;
1296 static int last_cmd = 0;
1303 if ((cmd == 0) || (cmd == 1)) {
1305 live.sg_flags |= CBREAK;
1306 live.sg_flags |= CRMOD;
1307 live.sg_flags |= NL1;
1308 live.sg_flags &= ~ECHO;
1310 live.sg_flags |= NOFLSH;
1314 gtty(0, &saved_settings);
1317 stty(0, &saved_settings);
1324 * display_help() - help file viewer
1326 void display_help(CtdlIPC *ipc, char *name)
1333 * fmout() - Citadel text formatter and paginator
1336 int width, /* screen width to use */
1337 FILE *fpin, /* file to read from, or NULL to format given text */
1338 char *text, /* text to be formatted (when fpin is NULL */
1339 FILE *fpout, /* file to write to, or NULL to write to screen */
1340 char pagin, /* nonzero if we should use the paginator */
1341 int height, /* screen height to use */
1342 int starting_lp,/* starting value for lines_printed, -1 for global */
1343 int subst) /* nonzero if we should use hypertext mode */
1345 char *buffer = NULL; /* The current message */
1346 char *word = NULL; /* What we are about to actually print */
1347 char *e; /* Pointer to position in text */
1348 char old = 0; /* The previous character */
1349 int column = 0; /* Current column */
1350 size_t i; /* Generic counter */
1352 /* Space for a single word, which can be at most screenwidth */
1353 word = (char *)calloc(1, width);
1355 scr_printf("Can't alloc memory to print message: %s!\n",
1360 /* Read the entire message body into memory */
1362 buffer = load_message_from_file(fpin);
1364 scr_printf("Can't print message: %s!\n",
1373 if (starting_lp >= 0)
1374 lines_printed = starting_lp;
1376 /* Run the message body */
1378 /* Catch characters that shouldn't be there at all */
1383 /* First, are we looking at a newline? */
1386 if (*e == ' ') { /* Paragraph */
1388 fprintf(fpout, "\n");
1392 lines_printed = checkpagin(lines_printed, pagin, height);
1395 } else if (old != ' ') {/* Don't print two spaces */
1397 fprintf(fpout, " ");
1407 /* Are we looking at a nonprintable?
1408 * (This section is now commented out because we could be displaying
1409 * a character set like UTF-8 or ISO-8859-1.)
1410 if ( (*e < 32) || (*e > 126) ) {
1415 /* Or are we looking at a space? */
1418 if (column >= width - 1) {
1419 /* Are we in the rightmost column? */
1421 fprintf(fpout, "\n");
1425 lines_printed = checkpagin(lines_printed, pagin, height);
1428 } else if (!(column == 0 && old == ' ')) {
1429 /* Eat only the first space on a line */
1431 fprintf(fpout, " ");
1437 /* ONLY eat the FIRST space on a line */
1443 /* Read a word, slightly messy */
1446 if (!isprint(e[i]) && !isspace(e[i]))
1453 /* We should never see these, but... slightly messy */
1454 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1457 /* Break up really long words */
1458 /* TODO: auto-hyphenation someday? */
1461 strncpy(word, e, i);
1464 /* Decide where to print the word */
1465 if (column + i >= width) {
1466 /* Wrap to the next line */
1468 fprintf(fpout, "\n");
1472 lines_printed = checkpagin(lines_printed, pagin, height);
1477 /* Print the word */
1479 fprintf(fpout, "%s", word);
1481 scr_printf("%s", word);
1484 e += i; /* Start over with the whitepsace! */
1488 if (fpin) /* We allocated this, remember? */
1491 /* Is this necessary? It makes the output kind of spacey. */
1493 fprintf(fpout, "\n");
1497 lines_printed = checkpagin(lines_printed, pagin, height);
1505 * support ANSI color if defined
1507 void color(int colornum)
1509 static int hold_color;
1510 static int current_color;
1512 if (colornum == COLOR_PUSH) {
1513 hold_color = current_color;
1517 if (colornum == COLOR_POP) {
1522 current_color = colornum;
1524 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1525 if (scr_color(colornum))
1528 /* When switching to dim white, actually output an 'original
1529 * pair' sequence -- this looks better on black-on-white
1530 * terminals. - Changed to ORIGINAL_PAIR as this actually
1531 * wound up looking horrible on black-on-white terminals, not
1532 * to mention transparent terminals.
1534 if (colornum == ORIGINAL_PAIR)
1535 printf("\033[0;39;49m");
1537 printf("\033[%d;3%d;4%dm",
1538 (colornum & 8) ? 1 : 0,
1546 void cls(int colornum)
1549 printf("\033[4%dm\033[2J\033[H\033[0m",
1550 colornum ? colornum : rc_color_use_bg);
1557 * Detect whether ANSI color is available (answerback)
1559 void send_ansi_detect(void)
1561 if (rc_ansi_color == 2) {
1568 void look_for_ansi(void)
1576 if (rc_ansi_color == 0) {
1578 } else if (rc_ansi_color == 1) {
1580 } else if (rc_ansi_color == 2) {
1582 /* otherwise, do the auto-detect */
1587 if ((now - AnsiDetect) < 2)
1596 select(1, &rfds, NULL, NULL, &tv);
1597 if (FD_ISSET(0, &rfds)) {
1598 abuf[strlen(abuf) + 1] = 0;
1599 rv = read(0, &abuf[strlen(abuf)], 1);
1601 } while (FD_ISSET(0, &rfds));
1603 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1604 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1605 && (abuf[a + 2] == '?')) {
1614 * Display key options (highlight hotkeys inside angle brackets)
1616 void keyopt(char *buf) {
1620 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1622 pprintf("%c", buf[i]);
1623 color(BRIGHT_MAGENTA);
1625 if (buf[i]=='>'&& buf[i+1] != '>') {
1628 pprintf("%c", buf[i]);
1637 * Present a key-menu line choice type of thing
1639 char keymenu(char *menuprompt, char *menustring) {
1645 int display_prompt = 1;
1647 choices = num_tokens(menustring, '|');
1649 if (menuprompt != NULL) do_prompt = 1;
1650 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1653 if (display_prompt) {
1655 scr_printf("%s ", menuprompt);
1658 for (i=0; i<choices; ++i) {
1659 extract_token(buf, menustring, i, '|', sizeof buf);
1669 if ( (do_prompt) && (ch=='?') ) {
1670 scr_printf("\rOne of... ");
1672 for (i=0; i<choices; ++i) {
1673 extract_token(buf, menustring, i, '|', sizeof buf);
1682 for (i=0; i<choices; ++i) {
1683 extract_token(buf, menustring, i, '|', sizeof buf);
1684 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1685 if ( (ch == tolower(buf[c]))
1687 && (buf[c+1]=='>') ) {
1688 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1689 if ( (a!=(c-1)) && (a!=(c+1))) {