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);
334 /* when running in curses mode, the scroll bar in most
335 xterm-style programs becomes useless, so it makes sense to
336 pause after a screenful of pages if the user has been idle
337 for a while. However, this is annoying to some of the users
338 who aren't in curses mode and tend to leave their clients
339 idle. keepalives become disabled, resulting in getting booted
340 when coming back to the idle session. but they probably have
341 a working scrollback in their terminal, so disable it in this
344 if (!is_curses_enabled())
347 scr_printf("\n---\n");
354 void set_keepalives(int s)
356 keepalives_enabled = (char) s;
360 * This loop handles the "keepalive" messages sent to the server when idling.
363 static time_t idlet = 0;
364 static void really_do_keepalive(void) {
365 int r; /* IPC response code */
369 /* This may sometimes get called before we are actually connected
370 * to the server. Don't do anything if we aren't connected. -IO
372 if (!ipc_for_signal_handlers)
375 /* If full keepalives are enabled, send a NOOP to the server and
376 * wait for a response.
378 if (keepalives_enabled == KA_YES) {
379 r = CtdlIPCNoop(ipc_for_signal_handlers);
380 if (instant_msgs > 0) {
381 if (ok_to_interrupt == 1) {
382 scr_printf("\r%64s\r", "");
384 scr_printf("%s%c ", room_name,
385 room_prompt(room_flags));
391 /* If half keepalives are enabled, send a QNOP to the server (if the
392 * server supports it) and then do nothing.
394 if ( (keepalives_enabled == KA_HALF)
395 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
396 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
400 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
401 encapsulated interface; in theory there should be no need to touch these
402 globals outside of the async_ka_* functions. */
404 #ifdef THREADED_CLIENT
405 static pthread_t ka_thr_handle;
406 static int ka_thr_active = 0;
407 static int async_ka_enabled = 0;
409 static void *ka_thread(void *arg)
411 #ifdef HAVE_BACKTRACE
412 char threadName[256];
415 sprintf(threadName, "ka_Thread n");
417 // Register for tracing
418 eCrash_RegisterThread(threadName, 0);
420 really_do_keepalive();
421 pthread_detach(ka_thr_handle);
424 #ifdef HAVE_BACKTRACE
425 eCrash_UnregisterThread();
430 /* start up a thread to handle a keepalive in the background */
431 static void async_ka_exec(void)
433 if (!ka_thr_active) {
435 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
436 perror("pthread_create");
441 #endif /* THREADED_CLIENT */
443 /* I changed this from static to not because I need to call it from
444 screen.c, either that or make something in screen.c not static.
445 Fix it how you like. Why all the staticness? stu */
447 void do_keepalive(void)
452 if ((now - idlet) < ((long) S_KEEPALIVE))
455 /* Do a space-backspace to keep telnet sessions from idling out */
456 scr_printf(" %c", 8);
459 #ifdef THREADED_CLIENT
460 if (async_ka_enabled)
464 really_do_keepalive();
468 /* Now the actual async-keepalve API that we expose to higher levels:
469 async_ka_start() and async_ka_end(). These do nothing when we don't have
470 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
472 /* wait for a background keepalive to complete. this must be done before
473 attempting any further server requests! */
474 void async_ka_end(void)
476 #ifdef THREADED_CLIENT
478 pthread_join(ka_thr_handle, NULL);
484 /* tell do_keepalive() that keepalives are asynchronous. */
485 void async_ka_start(void)
487 #ifdef THREADED_CLIENT
494 { /* get a character from the keyboard, with */
495 int a; /* the watchdog timer in effect if necessary */
505 /* This loop waits for keyboard input. If the keepalive
506 * timer expires, it sends a keepalive to the server if
507 * necessary and then waits again.
510 scr_set_windowsize(ipc_for_signal_handlers);
512 scr_set_windowsize(ipc_for_signal_handlers);
516 tv.tv_sec = S_KEEPALIVE;
519 select(1, &rfds, NULL, NULL, &tv);
520 } while (!FD_ISSET(0, &rfds));
522 /* At this point, there's input, so fetch it.
523 * (There's a hole in the bucket...)
525 a = scr_getc(SCR_BLOCK);
532 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
536 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
537 && ((a < 32) || (a > 126))) {
542 #ifndef DISABLE_CURSES
543 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
556 { /* Returns 1 for yes, 0 for no */
572 /* Returns 1 for yes, 0 for no, arg is default value */
595 /* Gets a line from the terminal */
596 /* string == Pointer to string buffer */
597 /* lim == Maximum length - if negative, no-show */
598 void ctdl_getline(char *string, int lim)
612 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
613 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
615 if ((a != 10) && (a != 8) && (strlen(string) == lim))
617 if ((a == 8) && (string[0] != 0)) {
618 string[strlen(string) - 1] = 0;
619 scr_putc(8); scr_putc(32); scr_putc(8);
622 if ((a == 23) && (string[0] != 0)) {
624 string[strlen(string) - 1] = 0;
625 scr_putc(8); scr_putc(32); scr_putc(8);
626 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
648 * strprompt() - prompt for a string, print the existing value and
649 * allow the user to press return to keep it...
651 void strprompt(char *prompt, char *str, int len)
658 scr_printf("%s ", prompt);
661 color(BRIGHT_MAGENTA);
664 scr_printf("%s", str);
667 for (i=0; !IsEmptyStr(&str[i]); ++i) {
677 ctdl_getline(buf, len);
684 * boolprompt() - prompt for a yes/no, print the existing value and
685 * allow the user to press return to keep it...
687 int boolprompt(char *prompt, int prev_val)
692 scr_printf("%s ", prompt);
695 color(BRIGHT_MAGENTA);
696 scr_printf("%s", (prev_val ? "Yes" : "No"));
700 r = (yesno_d(prev_val));
706 * intprompt() - like strprompt(), except for an integer
707 * (note that it RETURNS the new value!)
709 int intprompt(char *prompt, int ival, int imin, int imax)
717 snprintf(buf, sizeof buf, "%d", i);
718 strprompt(prompt, buf, 15);
720 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
721 if ( (!isdigit(buf[p]))
722 && ( (buf[p]!='-') || (p!=0) ) )
726 scr_printf("*** Must be no less than %d.\n", imin);
728 scr_printf("*** Must be no more than %d.\n", imax);
729 } while ((i < imin) || (i > imax));
734 * newprompt() - prompt for a string with no existing value
735 * (clears out string buffer first)
737 void newprompt(char *prompt, char *str, int len)
739 color(BRIGHT_MAGENTA);
740 scr_printf("%s", prompt);
742 ctdl_getline(str, len);
748 { /* returns a lower case value */
757 * parse the citadel.rc file
759 void load_command_set(void)
763 char editor_key[100];
765 struct citcmd *lastcmd = NULL;
771 /* first, set up some defaults for non-required variables */
773 for (i = 0; i < MAX_EDITORS; i++)
774 strcpy(editor_paths[i], "");
775 strcpy(printcmd, "");
776 strcpy(imagecmd, "");
777 strcpy(rc_username, "");
778 strcpy(rc_password, "");
781 rc_allow_attachments = 0;
782 rc_remember_passwords = 0;
783 strcpy(rc_exp_cmd, "");
784 rc_display_message_numbers = 0;
785 rc_force_mail_prompts = 0;
788 strcpy(rc_url_cmd, "");
789 strcpy(rc_open_cmd, "");
790 strcpy(rc_gotmail_cmd, "");
792 rc_encrypt = RC_DEFAULT;
794 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
795 rc_screen = RC_DEFAULT;
797 rc_alt_semantics = 0;
799 /* now try to open the citadel.rc file */
802 if (getenv("HOME") != NULL) {
803 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
804 ccfile = fopen(buf, "r");
806 if (ccfile == NULL) {
807 ccfile = fopen(file_citadel_rc, "r");
809 if (ccfile == NULL) {
810 ccfile = fopen("/etc/citadel.rc", "r");
812 if (ccfile == NULL) {
813 ccfile = fopen("./citadel.rc", "r");
815 if (ccfile == NULL) {
816 perror("commands: cannot open citadel.rc");
819 while (fgets(buf, sizeof buf, ccfile) != NULL) {
820 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
821 buf[strlen(buf) - 1] = 0;
823 if (!strncasecmp(buf, "encrypt=", 8)) {
824 if (!strcasecmp(&buf[8], "yes")) {
828 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
833 else if (!strcasecmp(&buf[8], "no")) {
836 else if (!strcasecmp(&buf[8], "default")) {
837 rc_encrypt = RC_DEFAULT;
842 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
843 if (!strncasecmp(buf, "fullscreen=", 11)) {
844 if (!strcasecmp(&buf[11], "yes"))
846 else if (!strcasecmp(&buf[11], "no"))
851 if (!strncasecmp(buf, "editor=", 7))
852 strcpy(editor_paths[0], &buf[7]);
854 for (i = 0; i < MAX_EDITORS; i++)
856 sprintf(editor_key, "editor%d=", i);
857 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
858 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
861 if (!strncasecmp(buf, "printcmd=", 9))
862 strcpy(printcmd, &buf[9]);
864 if (!strncasecmp(buf, "imagecmd=", 9))
865 strcpy(imagecmd, &buf[9]);
867 if (!strncasecmp(buf, "expcmd=", 7))
868 strcpy(rc_exp_cmd, &buf[7]);
870 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
871 have_xterm = (char) atoi(&buf[24]);
873 if (!strncasecmp(buf, "use_floors=", 11)) {
874 if (!strcasecmp(&buf[11], "yes"))
875 rc_floor_mode = RC_YES;
876 if (!strcasecmp(&buf[11], "no"))
877 rc_floor_mode = RC_NO;
878 if (!strcasecmp(&buf[11], "default"))
879 rc_floor_mode = RC_DEFAULT;
881 if (!strncasecmp(buf, "beep=", 5)) {
882 rc_exp_beep = atoi(&buf[5]);
884 if (!strncasecmp(buf, "allow_attachments=", 18)) {
885 rc_allow_attachments = atoi(&buf[18]);
887 if (!strncasecmp(buf, "idle_threshold=", 15)) {
888 rc_idle_threshold = atol(&buf[15]);
890 if (!strncasecmp(buf, "remember_passwords=", 19)) {
891 rc_remember_passwords = atoi(&buf[19]);
893 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
894 rc_display_message_numbers = atoi(&buf[24]);
896 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
897 rc_force_mail_prompts = atoi(&buf[19]);
899 if (!strncasecmp(buf, "ansi_color=", 11)) {
900 if (!strncasecmp(&buf[11], "on", 2))
902 if (!strncasecmp(&buf[11], "auto", 4))
903 rc_ansi_color = 2; /* autodetect */
904 if (!strncasecmp(&buf[11], "user", 4))
905 rc_ansi_color = 3; /* user config */
907 if (!strncasecmp(buf, "use_background=", 15)) {
908 if (!strncasecmp(&buf[15], "on", 2))
911 if (!strncasecmp(buf, "prompt_control=", 15)) {
912 if (!strncasecmp(&buf[15], "on", 2))
913 rc_prompt_control = 1;
914 if (!strncasecmp(&buf[15], "user", 4))
915 rc_prompt_control = 3; /* user config */
917 if (!strncasecmp(buf, "username=", 9))
918 strcpy(rc_username, &buf[9]);
920 if (!strncasecmp(buf, "password=", 9))
921 strcpy(rc_password, &buf[9]);
923 if (!strncasecmp(buf, "urlcmd=", 7))
924 strcpy(rc_url_cmd, &buf[7]);
926 if (!strncasecmp(buf, "opencmd=", 7))
927 strcpy(rc_open_cmd, &buf[8]);
929 if (!strncasecmp(buf, "gotmailcmd=", 11))
930 strcpy(rc_gotmail_cmd, &buf[11]);
932 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
933 if (!strncasecmp(&buf[20], "yes", 3)) {
934 rc_alt_semantics = 1;
937 rc_alt_semantics = 0;
941 if (!strncasecmp(buf, "cmd=", 4)) {
942 strcpy(buf, &buf[4]);
944 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
946 cptr->c_cmdnum = atoi(buf);
947 for (d = strlen(buf); d >= 0; --d)
950 strcpy(buf, &buf[b + 1]);
952 cptr->c_axlevel = atoi(buf);
953 for (d = strlen(buf); d >= 0; --d)
956 strcpy(buf, &buf[b + 1]);
958 for (a = 0; a < 5; ++a)
959 cptr->c_keys[a][0] = 0;
963 buf[strlen(buf) + 1] = 0;
964 while (!IsEmptyStr(buf)) {
966 for (d = strlen(buf); d >= 0; --d)
969 strncpy(cptr->c_keys[a], buf, b);
970 cptr->c_keys[a][b] = 0;
972 strcpy(buf, &buf[b + 1]);
982 lastcmd->next = cptr;
992 * return the key associated with a command
994 char keycmd(char *cmdstr)
998 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
999 if (cmdstr[a] == '&')
1000 return (tolower(cmdstr[a + 1]));
1006 * Output the string from a key command without the ampersand
1007 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
1009 char *cmd_expand(char *strbuf, int mode)
1012 static char exp[64];
1015 strcpy(exp, strbuf);
1017 for (a = 0; exp[a]; ++a) {
1018 if (strbuf[a] == '&') {
1020 /* dont echo these non mnemonic command keys */
1021 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1024 strcpy(&exp[a], &exp[a + 1 + noecho]);
1028 strcpy(buf, &exp[a + 2]);
1034 if (!strncmp(&exp[a], "^r", 2)) {
1036 strcpy(&exp[a], room_name);
1037 strcat(exp, &buf[a + 2]);
1039 if (!strncmp(&exp[a], "^c", 2)) {
1041 strcpy(&exp[a + 1], &exp[a + 2]);
1051 * Comparison function to determine if entered commands match a
1052 * command loaded from the config file.
1054 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1065 for (a = 0; a < ncomp; ++a) {
1066 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1067 || (cptr->c_axlevel > cmdax))
1075 * This function returns 1 if a given command requires a string input
1077 int requires_string(struct citcmd *cptr, int ncomp)
1082 strcpy(buf, cptr->c_keys[ncomp - 1]);
1083 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1092 * Input a command at the main prompt.
1093 * This function returns an integer command number. If the command prompts
1094 * for a string then it is placed in the supplied buffer.
1096 int getcmd(CtdlIPC *ipc, char *argbuf)
1105 struct citcmd *cptr;
1108 * Starting a new command now, so set sigcaught to 0. This variable
1109 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1110 * been interrupted by a keypress.
1114 /* Switch color support on or off if we're in user mode */
1115 if (rc_ansi_color == 3) {
1116 if (userflags & US_COLOR)
1121 /* if we're running in idiot mode, display a cute little menu */
1122 IFNEXPERT formout(ipc, "mainmenu");
1127 for (a = 0; a < 5; ++a)
1129 /* now the room prompt... */
1130 ok_to_interrupt = 1;
1131 color(BRIGHT_WHITE);
1132 scr_printf("\n%s", room_name);
1134 scr_printf("%c ", room_prompt(room_flags));
1139 ok_to_interrupt = 0;
1141 /* Handle the backspace key, but only if there's something
1142 * to backspace over...
1144 if ((ch == 8) && (cmdpos > 0)) {
1145 back(cmdspaces[cmdpos - 1] + 1);
1149 /* Spacebar invokes "lazy traversal" commands */
1150 if ((ch == 32) && (cmdpos == 0)) {
1151 this_lazy_cmd = next_lazy_cmd;
1152 if (this_lazy_cmd == 13)
1154 if (this_lazy_cmd == 5)
1156 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1157 if (cptr->c_cmdnum == this_lazy_cmd) {
1158 for (a = 0; a < 5; ++a)
1159 if (cptr->c_keys[a][0] != 0)
1160 scr_printf("%s ", cmd_expand(
1161 cptr->c_keys[a], 0));
1163 return (this_lazy_cmd);
1167 return (this_lazy_cmd);
1169 /* Otherwise, process the command */
1170 cmdbuf[cmdpos] = tolower(ch);
1172 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1173 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1175 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1176 cmdspaces[cmdpos] = strlen(
1177 cmd_expand(cptr->c_keys[cmdpos], 0));
1179 if ((cptr->c_keys[cmdpos + 1]) != 0)
1185 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1186 if (cmdmatch(cmdbuf, cptr, 5)) {
1187 /* We've found our command. */
1188 if (requires_string(cptr, cmdpos)) {
1189 ctdl_getline(argbuf, 64);
1194 /* If this command is one that changes rooms,
1195 * then the next lazy-command (space bar)
1196 * should be "read new" instead of "goto"
1198 if ((cptr->c_cmdnum == 5)
1199 || (cptr->c_cmdnum == 6)
1200 || (cptr->c_cmdnum == 47)
1201 || (cptr->c_cmdnum == 52)
1202 || (cptr->c_cmdnum == 16)
1203 || (cptr->c_cmdnum == 20))
1206 /* If this command is "read new"
1207 * then the next lazy-command (space bar)
1210 if (cptr->c_cmdnum == 13)
1213 return (cptr->c_cmdnum);
1219 pprintf("\rOne of ... \n");
1220 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1221 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1222 for (a = 0; a < 5; ++a) {
1223 keyopt(cmd_expand(cptr->c_keys[a], 1));
1231 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1233 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1234 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1235 for (a = 0; a < cmdpos; ++a) {
1237 cmd_expand(cptr->c_keys[a], 0));
1252 * set tty modes. commands are:
1254 * 01- set to Citadel mode
1255 * 2 - save current settings for later restoral
1256 * 3 - restore saved settings
1258 #ifdef HAVE_TERMIOS_H
1259 void stty_ctdl(int cmd)
1260 { /* SysV version of stty_ctdl() */
1261 struct termios live;
1262 static struct termios saved_settings;
1263 static int last_cmd = 0;
1270 if ((cmd == 0) || (cmd == 1)) {
1271 tcgetattr(0, &live);
1272 live.c_iflag = ISTRIP | IXON | IXANY;
1273 live.c_oflag = OPOST | ONLCR;
1274 live.c_lflag = ISIG | NOFLSH;
1276 live.c_cc[VINTR] = 0;
1277 live.c_cc[VQUIT] = 0;
1280 live.c_cc[VMIN] = 0;
1281 live.c_cc[VTIME] = 0;
1284 /* do we even need this stuff anymore? */
1285 /* live.c_line=0; */
1286 live.c_cc[VERASE] = 8;
1287 live.c_cc[VKILL] = 24;
1288 live.c_cc[VEOF] = 1;
1289 live.c_cc[VEOL] = 255;
1290 live.c_cc[VEOL2] = 0;
1291 live.c_cc[VSTART] = 0;
1292 tcsetattr(0, TCSADRAIN, &live);
1295 tcgetattr(0, &saved_settings);
1298 tcsetattr(0, TCSADRAIN, &saved_settings);
1303 void stty_ctdl(int cmd)
1304 { /* BSD version of stty_ctdl() */
1306 static struct sgttyb saved_settings;
1307 static int last_cmd = 0;
1314 if ((cmd == 0) || (cmd == 1)) {
1316 live.sg_flags |= CBREAK;
1317 live.sg_flags |= CRMOD;
1318 live.sg_flags |= NL1;
1319 live.sg_flags &= ~ECHO;
1321 live.sg_flags |= NOFLSH;
1325 gtty(0, &saved_settings);
1328 stty(0, &saved_settings);
1335 * display_help() - help file viewer
1337 void display_help(CtdlIPC *ipc, char *name)
1344 * fmout() - Citadel text formatter and paginator
1347 int width, /* screen width to use */
1348 FILE *fpin, /* file to read from, or NULL to format given text */
1349 char *text, /* text to be formatted (when fpin is NULL */
1350 FILE *fpout, /* file to write to, or NULL to write to screen */
1351 char pagin, /* nonzero if we should use the paginator */
1352 int height, /* screen height to use */
1353 int starting_lp,/* starting value for lines_printed, -1 for global */
1354 int subst) /* nonzero if we should use hypertext mode */
1356 char *buffer = NULL; /* The current message */
1357 char *word = NULL; /* What we are about to actually print */
1358 char *e; /* Pointer to position in text */
1359 char old = 0; /* The previous character */
1360 int column = 0; /* Current column */
1361 size_t i; /* Generic counter */
1363 /* Space for a single word, which can be at most screenwidth */
1364 word = (char *)calloc(1, width);
1366 err_printf("Can't alloc memory to print message: %s!\n",
1371 /* Read the entire message body into memory */
1373 buffer = load_message_from_file(fpin);
1375 err_printf("Can't print message: %s!\n",
1384 if (starting_lp >= 0)
1385 lines_printed = starting_lp;
1387 /* Run the message body */
1389 /* Catch characters that shouldn't be there at all */
1394 /* First, are we looking at a newline? */
1397 if (*e == ' ') { /* Paragraph */
1399 fprintf(fpout, "\n");
1403 lines_printed = checkpagin(lines_printed, pagin, height);
1406 } else if (old != ' ') {/* Don't print two spaces */
1408 fprintf(fpout, " ");
1418 /* Are we looking at a nonprintable?
1419 * (This section is now commented out because we could be displaying
1420 * a character set like UTF-8 or ISO-8859-1.)
1421 if ( (*e < 32) || (*e > 126) ) {
1426 /* Or are we looking at a space? */
1429 if (column >= width - 1) {
1430 /* Are we in the rightmost column? */
1432 fprintf(fpout, "\n");
1436 lines_printed = checkpagin(lines_printed, pagin, height);
1439 } else if (!(column == 0 && old == ' ')) {
1440 /* Eat only the first space on a line */
1442 fprintf(fpout, " ");
1448 /* ONLY eat the FIRST space on a line */
1454 /* Read a word, slightly messy */
1457 if (!isprint(e[i]) && !isspace(e[i]))
1464 /* We should never see these, but... slightly messy */
1465 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1468 /* Break up really long words */
1469 /* TODO: auto-hyphenation someday? */
1472 strncpy(word, e, i);
1475 /* Decide where to print the word */
1476 if (column + i >= width) {
1477 /* Wrap to the next line */
1479 fprintf(fpout, "\n");
1483 lines_printed = checkpagin(lines_printed, pagin, height);
1488 /* Print the word */
1490 fprintf(fpout, "%s", word);
1492 scr_printf("%s", word);
1495 e += i; /* Start over with the whitepsace! */
1499 if (fpin) /* We allocated this, remember? */
1502 /* Is this necessary? It makes the output kind of spacey. */
1504 fprintf(fpout, "\n");
1508 lines_printed = checkpagin(lines_printed, pagin, height);
1516 * support ANSI color if defined
1518 void color(int colornum)
1520 static int hold_color;
1521 static int current_color;
1523 if (colornum == COLOR_PUSH) {
1524 hold_color = current_color;
1528 if (colornum == COLOR_POP) {
1533 current_color = colornum;
1535 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1536 if (scr_color(colornum))
1539 /* When switching to dim white, actually output an 'original
1540 * pair' sequence -- this looks better on black-on-white
1541 * terminals. - Changed to ORIGINAL_PAIR as this actually
1542 * wound up looking horrible on black-on-white terminals, not
1543 * to mention transparent terminals.
1545 if (colornum == ORIGINAL_PAIR)
1546 printf("\033[0;39;49m");
1548 printf("\033[%d;3%d;4%dm",
1549 (colornum & 8) ? 1 : 0,
1557 void cls(int colornum)
1560 printf("\033[4%dm\033[2J\033[H\033[0m",
1561 colornum ? colornum : rc_color_use_bg);
1568 * Detect whether ANSI color is available (answerback)
1570 void send_ansi_detect(void)
1572 if (rc_ansi_color == 2) {
1579 void look_for_ansi(void)
1587 if (rc_ansi_color == 0) {
1589 } else if (rc_ansi_color == 1) {
1591 } else if (rc_ansi_color == 2) {
1593 /* otherwise, do the auto-detect */
1598 if ((now - AnsiDetect) < 2)
1607 select(1, &rfds, NULL, NULL, &tv);
1608 if (FD_ISSET(0, &rfds)) {
1609 abuf[strlen(abuf) + 1] = 0;
1610 rv = read(0, &abuf[strlen(abuf)], 1);
1612 } while (FD_ISSET(0, &rfds));
1614 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1615 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1616 && (abuf[a + 2] == '?')) {
1625 * Display key options (highlight hotkeys inside angle brackets)
1627 void keyopt(char *buf) {
1631 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1633 pprintf("%c", buf[i]);
1634 color(BRIGHT_MAGENTA);
1636 if (buf[i]=='>'&& buf[i+1] != '>') {
1639 pprintf("%c", buf[i]);
1648 * Present a key-menu line choice type of thing
1650 char keymenu(char *menuprompt, char *menustring) {
1656 int display_prompt = 1;
1658 choices = num_tokens(menustring, '|');
1660 if (menuprompt != NULL) do_prompt = 1;
1661 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1664 if (display_prompt) {
1666 scr_printf("%s ", menuprompt);
1669 for (i=0; i<choices; ++i) {
1670 extract_token(buf, menustring, i, '|', sizeof buf);
1680 if ( (do_prompt) && (ch=='?') ) {
1681 scr_printf("\rOne of... ");
1683 for (i=0; i<choices; ++i) {
1684 extract_token(buf, menustring, i, '|', sizeof buf);
1693 for (i=0; i<choices; ++i) {
1694 extract_token(buf, menustring, i, '|', sizeof buf);
1695 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1696 if ( (ch == tolower(buf[c]))
1698 && (buf[c+1]=='>') ) {
1699 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1700 if ( (a!=(c-1)) && (a!=(c+1))) {