4 * This file contains functions which implement parts of the
5 * text-mode user interface.
7 * Copyright (c) 1987-2009 by the citadel.org team
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31 #include <sys/types.h>
33 #if TIME_WITH_SYS_TIME
34 # include <sys/time.h>
38 # include <sys/time.h>
50 #ifdef HAVE_SYS_SELECT_H
51 #include <sys/select.h>
54 #ifdef THREADED_CLIENT
61 #include <libcitadel.h>
63 #include "citadel_ipc.h"
66 #include "citadel_decls.h"
68 #include "routines2.h"
70 #include "client_chat.h"
71 #include "citadel_dirs.h"
85 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
89 char rc_exp_cmd[1024];
90 int rc_allow_attachments;
91 int rc_display_message_numbers;
92 int rc_force_mail_prompts;
93 int rc_remember_passwords;
96 int rc_prompt_control = 0;
97 time_t rc_idle_threshold = (time_t)900;
99 char rc_open_cmd[SIZ];
100 char rc_gotmail_cmd[SIZ];
103 int next_lazy_cmd = 5;
105 int lines_printed = 0; /* line count for paginator */
106 extern int screenwidth, screenheight;
108 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
110 struct citcmd *cmdlist = NULL;
113 /* these variables are local to this module */
114 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
115 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
116 time_t AnsiDetect; /* when did we send the detect code? */
117 int enable_color = 0; /* nonzero for ANSI color */
123 * If an interesting key has been pressed, return its value, otherwise 0
125 char was_a_key_pressed(void) {
135 retval = select(1, &rfds, NULL, NULL, &tv);
137 /* Careful! Disable keepalives during keyboard polling; we're probably
138 * in the middle of a data transfer from the server, in which case
139 * sending a NOOP would throw the client protocol out of sync.
141 if (FD_ISSET(0, &rfds)) {
142 set_keepalives(KA_NO);
143 the_character = inkey();
144 set_keepalives(KA_YES);
149 return(the_character);
157 * Check to see if we need to pause at the end of a screen.
158 * If we do, we have to switch to half keepalives during the pause because
159 * we are probably in the middle of a server operation and the NOOP command
160 * would confuse everything.
162 int checkpagin(int lp, unsigned int pagin, unsigned int height)
166 if (sigcaught) return(lp);
167 thekey = was_a_key_pressed();
168 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
170 if (thekey == 'n' || thekey == 'N')
172 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
173 if (sigcaught) return(lp);
175 if (!pagin) return(0);
176 if (lp>=(height-1)) {
177 set_keepalives(KA_HALF);
178 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
179 set_keepalives(KA_YES);
189 * pprintf() ... paginated version of printf()
191 void pprintf(const char *format, ...) {
193 static char buf[4096]; /* static for performance, change if needed */
196 /* If sigcaught is nonzero, a keypress has interrupted this and we
197 * should just drain output.
199 if (sigcaught) return;
201 /* Otherwise, start spewing... */
202 va_start(arg_ptr, format);
203 vsnprintf(buf, sizeof(buf), format, arg_ptr);
206 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
210 lines_printed = checkpagin(lines_printed,
211 (userflags & US_PAGINATOR),
220 * print_instant() - print instant messages if there are any
222 void print_instant(void)
231 char *listing = NULL;
232 int r; /* IPC result code */
234 if (instant_msgs == 0)
240 if (IsEmptyStr(rc_exp_cmd)) {
245 while (instant_msgs != 0) {
246 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
250 instant_msgs = extract_int(buf, 0);
251 timestamp = extract_long(buf, 1);
252 flags = extract_int(buf, 2);
253 extract_token(sender, buf, 3, '|', sizeof sender);
254 extract_token(node, buf, 4, '|', sizeof node);
255 strcpy(last_paged, sender);
257 localtime_r(×tamp, &stamp);
259 /* If the page is a Logoff Request, honor it. */
265 if (!IsEmptyStr(rc_exp_cmd)) {
266 outpipe = popen(rc_exp_cmd, "w");
267 if (outpipe != NULL) {
268 /* Header derived from flags */
271 "Please log off now, as requested ");
273 fprintf(outpipe, "Broadcast message ");
275 fprintf(outpipe, "Chat request ");
277 fprintf(outpipe, "Message ");
278 /* Timestamp. Can this be improved? */
279 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
280 fprintf(outpipe, "at 12:%02d%cm",
282 stamp.tm_hour ? 'p' : 'a');
283 else if (stamp.tm_hour > 12) /* pm */
284 fprintf(outpipe, "at %d:%02dpm",
288 fprintf(outpipe, "at %d:%02dam",
289 stamp.tm_hour, stamp.tm_min);
290 fprintf(outpipe, " from %s", sender);
291 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
292 fprintf(outpipe, " @%s", node);
293 fprintf(outpipe, ":\n%s\n", listing);
295 if (instant_msgs == 0)
300 /* fall back to built-in instant message display */
304 /* Header derived from flags */
306 scr_printf("Please log off now, as requested ");
308 scr_printf("Broadcast message ");
310 scr_printf("Chat request ");
312 scr_printf("Message ");
314 /* Timestamp. Can this be improved? */
315 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
316 scr_printf("at 12:%02d%cm", stamp.tm_min,
317 stamp.tm_hour ? 'p' : 'a');
318 else if (stamp.tm_hour > 12) /* pm */
319 scr_printf("at %d:%02dpm",
320 stamp.tm_hour - 12, stamp.tm_min);
322 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
325 scr_printf(" from %s", sender);
327 /* Remote node, if any */
328 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
329 scr_printf(" @%s", node);
333 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
336 /* when running in curses mode, the scroll bar in most
337 xterm-style programs becomes useless, so it makes sense to
338 pause after a screenful of pages if the user has been idle
339 for a while. However, this is annoying to some of the users
340 who aren't in curses mode and tend to leave their clients
341 idle. keepalives become disabled, resulting in getting booted
342 when coming back to the idle session. but they probably have
343 a working scrollback in their terminal, so disable it in this
346 if (!is_curses_enabled())
349 scr_printf("\n---\n");
356 void set_keepalives(int s)
358 keepalives_enabled = (char) s;
362 * This loop handles the "keepalive" messages sent to the server when idling.
365 static time_t idlet = 0;
366 static void really_do_keepalive(void) {
367 int r; /* IPC response code */
371 /* This may sometimes get called before we are actually connected
372 * to the server. Don't do anything if we aren't connected. -IO
374 if (!ipc_for_signal_handlers)
377 /* If full keepalives are enabled, send a NOOP to the server and
378 * wait for a response.
380 if (keepalives_enabled == KA_YES) {
381 r = CtdlIPCNoop(ipc_for_signal_handlers);
382 if (instant_msgs > 0) {
383 if (ok_to_interrupt == 1) {
384 scr_printf("\r%64s\r", "");
386 scr_printf("%s%c ", room_name,
387 room_prompt(room_flags));
393 /* If half keepalives are enabled, send a QNOP to the server (if the
394 * server supports it) and then do nothing.
396 if ( (keepalives_enabled == KA_HALF)
397 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
398 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
402 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
403 encapsulated interface; in theory there should be no need to touch these
404 globals outside of the async_ka_* functions. */
406 #ifdef THREADED_CLIENT
407 static pthread_t ka_thr_handle;
408 static int ka_thr_active = 0;
409 static int async_ka_enabled = 0;
411 static void *ka_thread(void *arg)
413 #ifdef HAVE_BACKTRACE
414 char threadName[256];
417 sprintf(threadName, "ka_Thread n");
419 // Register for tracing
420 eCrash_RegisterThread(threadName, 0);
422 really_do_keepalive();
423 pthread_detach(ka_thr_handle);
426 #ifdef HAVE_BACKTRACE
427 eCrash_UnregisterThread();
432 /* start up a thread to handle a keepalive in the background */
433 static void async_ka_exec(void)
435 if (!ka_thr_active) {
437 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
438 perror("pthread_create");
443 #endif /* THREADED_CLIENT */
445 /* I changed this from static to not because I need to call it from
446 screen.c, either that or make something in screen.c not static.
447 Fix it how you like. Why all the staticness? stu */
449 void do_keepalive(void)
454 if ((now - idlet) < ((long) S_KEEPALIVE))
457 /* Do a space-backspace to keep telnet sessions from idling out */
458 scr_printf(" %c", 8);
461 #ifdef THREADED_CLIENT
462 if (async_ka_enabled)
466 really_do_keepalive();
470 /* Now the actual async-keepalve API that we expose to higher levels:
471 async_ka_start() and async_ka_end(). These do nothing when we don't have
472 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
474 /* wait for a background keepalive to complete. this must be done before
475 attempting any further server requests! */
476 void async_ka_end(void)
478 #ifdef THREADED_CLIENT
480 pthread_join(ka_thr_handle, NULL);
486 /* tell do_keepalive() that keepalives are asynchronous. */
487 void async_ka_start(void)
489 #ifdef THREADED_CLIENT
496 { /* get a character from the keyboard, with */
497 int a; /* the watchdog timer in effect if necessary */
507 /* This loop waits for keyboard input. If the keepalive
508 * timer expires, it sends a keepalive to the server if
509 * necessary and then waits again.
512 scr_set_windowsize(ipc_for_signal_handlers);
514 scr_set_windowsize(ipc_for_signal_handlers);
518 tv.tv_sec = S_KEEPALIVE;
521 select(1, &rfds, NULL, NULL, &tv);
522 } while (!FD_ISSET(0, &rfds));
524 /* At this point, there's input, so fetch it.
525 * (There's a hole in the bucket...)
527 a = scr_getc(SCR_BLOCK);
534 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
538 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
539 && ((a < 32) || (a > 126))) {
544 #ifndef DISABLE_CURSES
545 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
558 { /* Returns 1 for yes, 0 for no */
574 /* Returns 1 for yes, 0 for no, arg is default value */
597 /* Gets a line from the terminal */
598 /* string == Pointer to string buffer */
599 /* lim == Maximum length - if negative, no-show */
600 void ctdl_getline(char *string, int lim)
614 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
615 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
617 if ((a != 10) && (a != 8) && (strlen(string) == lim))
619 if ((a == 8) && (string[0] != 0)) {
620 string[strlen(string) - 1] = 0;
621 scr_putc(8); scr_putc(32); scr_putc(8);
624 if ((a == 23) && (string[0] != 0)) {
626 string[strlen(string) - 1] = 0;
627 scr_putc(8); scr_putc(32); scr_putc(8);
628 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
650 * strprompt() - prompt for a string, print the existing value and
651 * allow the user to press return to keep it...
653 void strprompt(char *prompt, char *str, int len)
660 scr_printf("%s ", prompt);
663 color(BRIGHT_MAGENTA);
666 scr_printf("%s", str);
669 for (i=0; !IsEmptyStr(&str[i]); ++i) {
679 ctdl_getline(buf, len);
686 * boolprompt() - prompt for a yes/no, print the existing value and
687 * allow the user to press return to keep it...
689 int boolprompt(char *prompt, int prev_val)
694 scr_printf("%s ", prompt);
697 color(BRIGHT_MAGENTA);
698 scr_printf("%s", (prev_val ? "Yes" : "No"));
702 r = (yesno_d(prev_val));
708 * intprompt() - like strprompt(), except for an integer
709 * (note that it RETURNS the new value!)
711 int intprompt(char *prompt, int ival, int imin, int imax)
719 snprintf(buf, sizeof buf, "%d", i);
720 strprompt(prompt, buf, 15);
722 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
723 if ( (!isdigit(buf[p]))
724 && ( (buf[p]!='-') || (p!=0) ) )
728 scr_printf("*** Must be no less than %d.\n", imin);
730 scr_printf("*** Must be no more than %d.\n", imax);
731 } while ((i < imin) || (i > imax));
736 * newprompt() - prompt for a string with no existing value
737 * (clears out string buffer first)
739 void newprompt(char *prompt, char *str, int len)
741 color(BRIGHT_MAGENTA);
742 scr_printf("%s", prompt);
744 ctdl_getline(str, len);
750 { /* returns a lower case value */
759 * parse the citadel.rc file
761 void load_command_set(void)
765 char editor_key[100];
767 struct citcmd *lastcmd = NULL;
773 /* first, set up some defaults for non-required variables */
775 for (i = 0; i < MAX_EDITORS; i++)
776 strcpy(editor_paths[i], "");
777 strcpy(printcmd, "");
778 strcpy(imagecmd, "");
779 strcpy(rc_username, "");
780 strcpy(rc_password, "");
783 rc_allow_attachments = 0;
784 rc_remember_passwords = 0;
785 strcpy(rc_exp_cmd, "");
786 rc_display_message_numbers = 0;
787 rc_force_mail_prompts = 0;
790 strcpy(rc_url_cmd, "");
791 strcpy(rc_open_cmd, "");
792 strcpy(rc_gotmail_cmd, "");
794 rc_encrypt = RC_DEFAULT;
796 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
797 rc_screen = RC_DEFAULT;
799 rc_alt_semantics = 0;
801 /* now try to open the citadel.rc file */
804 if (getenv("HOME") != NULL) {
805 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
806 ccfile = fopen(buf, "r");
808 if (ccfile == NULL) {
809 ccfile = fopen(file_citadel_rc, "r");
811 if (ccfile == NULL) {
812 ccfile = fopen("/etc/citadel.rc", "r");
814 if (ccfile == NULL) {
815 ccfile = fopen("./citadel.rc", "r");
817 if (ccfile == NULL) {
818 perror("commands: cannot open citadel.rc");
821 while (fgets(buf, sizeof buf, ccfile) != NULL) {
822 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
823 buf[strlen(buf) - 1] = 0;
825 if (!strncasecmp(buf, "encrypt=", 8)) {
826 if (!strcasecmp(&buf[8], "yes")) {
830 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
835 else if (!strcasecmp(&buf[8], "no")) {
838 else if (!strcasecmp(&buf[8], "default")) {
839 rc_encrypt = RC_DEFAULT;
844 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
845 if (!strncasecmp(buf, "fullscreen=", 11)) {
846 if (!strcasecmp(&buf[11], "yes"))
848 else if (!strcasecmp(&buf[11], "no"))
853 if (!strncasecmp(buf, "editor=", 7))
854 strcpy(editor_paths[0], &buf[7]);
856 for (i = 0; i < MAX_EDITORS; i++)
858 sprintf(editor_key, "editor%d=", i);
859 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
860 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
863 if (!strncasecmp(buf, "printcmd=", 9))
864 strcpy(printcmd, &buf[9]);
866 if (!strncasecmp(buf, "imagecmd=", 9))
867 strcpy(imagecmd, &buf[9]);
869 if (!strncasecmp(buf, "expcmd=", 7))
870 strcpy(rc_exp_cmd, &buf[7]);
872 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
873 have_xterm = (char) atoi(&buf[24]);
875 if (!strncasecmp(buf, "use_floors=", 11)) {
876 if (!strcasecmp(&buf[11], "yes"))
877 rc_floor_mode = RC_YES;
878 if (!strcasecmp(&buf[11], "no"))
879 rc_floor_mode = RC_NO;
880 if (!strcasecmp(&buf[11], "default"))
881 rc_floor_mode = RC_DEFAULT;
883 if (!strncasecmp(buf, "beep=", 5)) {
884 rc_exp_beep = atoi(&buf[5]);
886 if (!strncasecmp(buf, "allow_attachments=", 18)) {
887 rc_allow_attachments = atoi(&buf[18]);
889 if (!strncasecmp(buf, "idle_threshold=", 15)) {
890 rc_idle_threshold = atol(&buf[15]);
892 if (!strncasecmp(buf, "remember_passwords=", 19)) {
893 rc_remember_passwords = atoi(&buf[19]);
895 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
896 rc_display_message_numbers = atoi(&buf[24]);
898 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
899 rc_force_mail_prompts = atoi(&buf[19]);
901 if (!strncasecmp(buf, "ansi_color=", 11)) {
902 if (!strncasecmp(&buf[11], "on", 2))
904 if (!strncasecmp(&buf[11], "auto", 4))
905 rc_ansi_color = 2; /* autodetect */
906 if (!strncasecmp(&buf[11], "user", 4))
907 rc_ansi_color = 3; /* user config */
909 if (!strncasecmp(buf, "use_background=", 15)) {
910 if (!strncasecmp(&buf[15], "on", 2))
913 if (!strncasecmp(buf, "prompt_control=", 15)) {
914 if (!strncasecmp(&buf[15], "on", 2))
915 rc_prompt_control = 1;
916 if (!strncasecmp(&buf[15], "user", 4))
917 rc_prompt_control = 3; /* user config */
919 if (!strncasecmp(buf, "username=", 9))
920 strcpy(rc_username, &buf[9]);
922 if (!strncasecmp(buf, "password=", 9))
923 strcpy(rc_password, &buf[9]);
925 if (!strncasecmp(buf, "urlcmd=", 7))
926 strcpy(rc_url_cmd, &buf[7]);
928 if (!strncasecmp(buf, "opencmd=", 7))
929 strcpy(rc_open_cmd, &buf[8]);
931 if (!strncasecmp(buf, "gotmailcmd=", 11))
932 strcpy(rc_gotmail_cmd, &buf[11]);
934 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
935 if (!strncasecmp(&buf[20], "yes", 3)) {
936 rc_alt_semantics = 1;
939 rc_alt_semantics = 0;
943 if (!strncasecmp(buf, "cmd=", 4)) {
944 strcpy(buf, &buf[4]);
946 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
948 cptr->c_cmdnum = atoi(buf);
949 for (d = strlen(buf); d >= 0; --d)
952 strcpy(buf, &buf[b + 1]);
954 cptr->c_axlevel = atoi(buf);
955 for (d = strlen(buf); d >= 0; --d)
958 strcpy(buf, &buf[b + 1]);
960 for (a = 0; a < 5; ++a)
961 cptr->c_keys[a][0] = 0;
965 buf[strlen(buf) + 1] = 0;
966 while (!IsEmptyStr(buf)) {
968 for (d = strlen(buf); d >= 0; --d)
971 strncpy(cptr->c_keys[a], buf, b);
972 cptr->c_keys[a][b] = 0;
974 strcpy(buf, &buf[b + 1]);
984 lastcmd->next = cptr;
994 * return the key associated with a command
996 char keycmd(char *cmdstr)
1000 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
1001 if (cmdstr[a] == '&')
1002 return (tolower(cmdstr[a + 1]));
1008 * Output the string from a key command without the ampersand
1009 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
1011 char *cmd_expand(char *strbuf, int mode)
1014 static char exp[64];
1017 strcpy(exp, strbuf);
1019 for (a = 0; exp[a]; ++a) {
1020 if (strbuf[a] == '&') {
1022 /* dont echo these non mnemonic command keys */
1023 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1026 strcpy(&exp[a], &exp[a + 1 + noecho]);
1030 strcpy(buf, &exp[a + 2]);
1036 if (!strncmp(&exp[a], "^r", 2)) {
1038 strcpy(&exp[a], room_name);
1039 strcat(exp, &buf[a + 2]);
1041 if (!strncmp(&exp[a], "^c", 2)) {
1043 strcpy(&exp[a + 1], &exp[a + 2]);
1053 * Comparison function to determine if entered commands match a
1054 * command loaded from the config file.
1056 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1067 for (a = 0; a < ncomp; ++a) {
1068 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1069 || (cptr->c_axlevel > cmdax))
1077 * This function returns 1 if a given command requires a string input
1079 int requires_string(struct citcmd *cptr, int ncomp)
1084 strcpy(buf, cptr->c_keys[ncomp - 1]);
1085 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1094 * Input a command at the main prompt.
1095 * This function returns an integer command number. If the command prompts
1096 * for a string then it is placed in the supplied buffer.
1098 int getcmd(CtdlIPC *ipc, char *argbuf)
1107 struct citcmd *cptr;
1110 * Starting a new command now, so set sigcaught to 0. This variable
1111 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1112 * been interrupted by a keypress.
1116 /* Switch color support on or off if we're in user mode */
1117 if (rc_ansi_color == 3) {
1118 if (userflags & US_COLOR)
1123 /* if we're running in idiot mode, display a cute little menu */
1124 IFNEXPERT formout(ipc, "mainmenu");
1129 for (a = 0; a < 5; ++a)
1131 /* now the room prompt... */
1132 ok_to_interrupt = 1;
1133 color(BRIGHT_WHITE);
1134 scr_printf("\n%s", room_name);
1136 scr_printf("%c ", room_prompt(room_flags));
1141 ok_to_interrupt = 0;
1143 /* Handle the backspace key, but only if there's something
1144 * to backspace over...
1146 if ((ch == 8) && (cmdpos > 0)) {
1147 back(cmdspaces[cmdpos - 1] + 1);
1151 /* Spacebar invokes "lazy traversal" commands */
1152 if ((ch == 32) && (cmdpos == 0)) {
1153 this_lazy_cmd = next_lazy_cmd;
1154 if (this_lazy_cmd == 13)
1156 if (this_lazy_cmd == 5)
1158 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1159 if (cptr->c_cmdnum == this_lazy_cmd) {
1160 for (a = 0; a < 5; ++a)
1161 if (cptr->c_keys[a][0] != 0)
1162 scr_printf("%s ", cmd_expand(
1163 cptr->c_keys[a], 0));
1165 return (this_lazy_cmd);
1169 return (this_lazy_cmd);
1171 /* Otherwise, process the command */
1172 cmdbuf[cmdpos] = tolower(ch);
1174 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1175 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1177 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1178 cmdspaces[cmdpos] = strlen(
1179 cmd_expand(cptr->c_keys[cmdpos], 0));
1181 if ((cptr->c_keys[cmdpos + 1]) != 0)
1187 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1188 if (cmdmatch(cmdbuf, cptr, 5)) {
1189 /* We've found our command. */
1190 if (requires_string(cptr, cmdpos)) {
1191 ctdl_getline(argbuf, 64);
1196 /* If this command is one that changes rooms,
1197 * then the next lazy-command (space bar)
1198 * should be "read new" instead of "goto"
1200 if ((cptr->c_cmdnum == 5)
1201 || (cptr->c_cmdnum == 6)
1202 || (cptr->c_cmdnum == 47)
1203 || (cptr->c_cmdnum == 52)
1204 || (cptr->c_cmdnum == 16)
1205 || (cptr->c_cmdnum == 20))
1208 /* If this command is "read new"
1209 * then the next lazy-command (space bar)
1212 if (cptr->c_cmdnum == 13)
1215 return (cptr->c_cmdnum);
1221 pprintf("\rOne of ... \n");
1222 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1223 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1224 for (a = 0; a < 5; ++a) {
1225 keyopt(cmd_expand(cptr->c_keys[a], 1));
1233 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1235 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1236 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1237 for (a = 0; a < cmdpos; ++a) {
1239 cmd_expand(cptr->c_keys[a], 0));
1254 * set tty modes. commands are:
1256 * 01- set to Citadel mode
1257 * 2 - save current settings for later restoral
1258 * 3 - restore saved settings
1260 #ifdef HAVE_TERMIOS_H
1261 void stty_ctdl(int cmd)
1262 { /* SysV version of stty_ctdl() */
1263 struct termios live;
1264 static struct termios saved_settings;
1265 static int last_cmd = 0;
1272 if ((cmd == 0) || (cmd == 1)) {
1273 tcgetattr(0, &live);
1274 live.c_iflag = ISTRIP | IXON | IXANY;
1275 live.c_oflag = OPOST | ONLCR;
1276 live.c_lflag = ISIG | NOFLSH;
1278 live.c_cc[VINTR] = 0;
1279 live.c_cc[VQUIT] = 0;
1282 live.c_cc[VMIN] = 0;
1283 live.c_cc[VTIME] = 0;
1286 /* do we even need this stuff anymore? */
1287 /* live.c_line=0; */
1288 live.c_cc[VERASE] = 8;
1289 live.c_cc[VKILL] = 24;
1290 live.c_cc[VEOF] = 1;
1291 live.c_cc[VEOL] = 255;
1292 live.c_cc[VEOL2] = 0;
1293 live.c_cc[VSTART] = 0;
1294 tcsetattr(0, TCSADRAIN, &live);
1297 tcgetattr(0, &saved_settings);
1300 tcsetattr(0, TCSADRAIN, &saved_settings);
1305 void stty_ctdl(int cmd)
1306 { /* BSD version of stty_ctdl() */
1308 static struct sgttyb saved_settings;
1309 static int last_cmd = 0;
1316 if ((cmd == 0) || (cmd == 1)) {
1318 live.sg_flags |= CBREAK;
1319 live.sg_flags |= CRMOD;
1320 live.sg_flags |= NL1;
1321 live.sg_flags &= ~ECHO;
1323 live.sg_flags |= NOFLSH;
1327 gtty(0, &saved_settings);
1330 stty(0, &saved_settings);
1337 * display_help() - help file viewer
1339 void display_help(CtdlIPC *ipc, char *name)
1346 * fmout() - Citadel text formatter and paginator
1349 int width, /* screen width to use */
1350 FILE *fpin, /* file to read from, or NULL to format given text */
1351 char *text, /* text to be formatted (when fpin is NULL */
1352 FILE *fpout, /* file to write to, or NULL to write to screen */
1353 char pagin, /* nonzero if we should use the paginator */
1354 int height, /* screen height to use */
1355 int starting_lp,/* starting value for lines_printed, -1 for global */
1356 int subst) /* nonzero if we should use hypertext mode */
1358 char *buffer = NULL; /* The current message */
1359 char *word = NULL; /* What we are about to actually print */
1360 char *e; /* Pointer to position in text */
1361 char old = 0; /* The previous character */
1362 int column = 0; /* Current column */
1363 size_t i; /* Generic counter */
1365 /* Space for a single word, which can be at most screenwidth */
1366 word = (char *)calloc(1, width);
1368 err_printf("Can't alloc memory to print message: %s!\n",
1373 /* Read the entire message body into memory */
1375 buffer = load_message_from_file(fpin);
1377 err_printf("Can't print message: %s!\n",
1386 if (starting_lp >= 0)
1387 lines_printed = starting_lp;
1389 /* Run the message body */
1391 /* Catch characters that shouldn't be there at all */
1396 /* First, are we looking at a newline? */
1399 if (*e == ' ') { /* Paragraph */
1401 fprintf(fpout, "\n");
1405 lines_printed = checkpagin(lines_printed, pagin, height);
1408 } else if (old != ' ') {/* Don't print two spaces */
1410 fprintf(fpout, " ");
1420 /* Are we looking at a nonprintable?
1421 * (This section is now commented out because we could be displaying
1422 * a character set like UTF-8 or ISO-8859-1.)
1423 if ( (*e < 32) || (*e > 126) ) {
1428 /* Or are we looking at a space? */
1431 if (column >= width - 1) {
1432 /* Are we in the rightmost column? */
1434 fprintf(fpout, "\n");
1438 lines_printed = checkpagin(lines_printed, pagin, height);
1441 } else if (!(column == 0 && old == ' ')) {
1442 /* Eat only the first space on a line */
1444 fprintf(fpout, " ");
1450 /* ONLY eat the FIRST space on a line */
1456 /* Read a word, slightly messy */
1459 if (!isprint(e[i]) && !isspace(e[i]))
1466 /* We should never see these, but... slightly messy */
1467 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1470 /* Break up really long words */
1471 /* TODO: auto-hyphenation someday? */
1474 strncpy(word, e, i);
1477 /* Decide where to print the word */
1478 if (column + i >= width) {
1479 /* Wrap to the next line */
1481 fprintf(fpout, "\n");
1485 lines_printed = checkpagin(lines_printed, pagin, height);
1490 /* Print the word */
1492 fprintf(fpout, "%s", word);
1494 scr_printf("%s", word);
1497 e += i; /* Start over with the whitepsace! */
1501 if (fpin) /* We allocated this, remember? */
1504 /* Is this necessary? It makes the output kind of spacey. */
1506 fprintf(fpout, "\n");
1510 lines_printed = checkpagin(lines_printed, pagin, height);
1518 * support ANSI color if defined
1520 void color(int colornum)
1522 static int hold_color;
1523 static int current_color;
1525 if (colornum == COLOR_PUSH) {
1526 hold_color = current_color;
1530 if (colornum == COLOR_POP) {
1535 current_color = colornum;
1537 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1538 if (scr_color(colornum))
1541 /* When switching to dim white, actually output an 'original
1542 * pair' sequence -- this looks better on black-on-white
1543 * terminals. - Changed to ORIGINAL_PAIR as this actually
1544 * wound up looking horrible on black-on-white terminals, not
1545 * to mention transparent terminals.
1547 if (colornum == ORIGINAL_PAIR)
1548 printf("\033[0;39;49m");
1550 printf("\033[%d;3%d;4%dm",
1551 (colornum & 8) ? 1 : 0,
1559 void cls(int colornum)
1562 printf("\033[4%dm\033[2J\033[H\033[0m",
1563 colornum ? colornum : rc_color_use_bg);
1570 * Detect whether ANSI color is available (answerback)
1572 void send_ansi_detect(void)
1574 if (rc_ansi_color == 2) {
1581 void look_for_ansi(void)
1589 if (rc_ansi_color == 0) {
1591 } else if (rc_ansi_color == 1) {
1593 } else if (rc_ansi_color == 2) {
1595 /* otherwise, do the auto-detect */
1600 if ((now - AnsiDetect) < 2)
1609 select(1, &rfds, NULL, NULL, &tv);
1610 if (FD_ISSET(0, &rfds)) {
1611 abuf[strlen(abuf) + 1] = 0;
1612 rv = read(0, &abuf[strlen(abuf)], 1);
1614 } while (FD_ISSET(0, &rfds));
1616 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1617 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1618 && (abuf[a + 2] == '?')) {
1627 * Display key options (highlight hotkeys inside angle brackets)
1629 void keyopt(char *buf) {
1633 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1635 pprintf("%c", buf[i]);
1636 color(BRIGHT_MAGENTA);
1638 if (buf[i]=='>'&& buf[i+1] != '>') {
1641 pprintf("%c", buf[i]);
1650 * Present a key-menu line choice type of thing
1652 char keymenu(char *menuprompt, char *menustring) {
1658 int display_prompt = 1;
1660 choices = num_tokens(menustring, '|');
1662 if (menuprompt != NULL) do_prompt = 1;
1663 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1666 if (display_prompt) {
1668 scr_printf("%s ", menuprompt);
1671 for (i=0; i<choices; ++i) {
1672 extract_token(buf, menustring, i, '|', sizeof buf);
1682 if ( (do_prompt) && (ch=='?') ) {
1683 scr_printf("\rOne of... ");
1685 for (i=0; i<choices; ++i) {
1686 extract_token(buf, menustring, i, '|', sizeof buf);
1695 for (i=0; i<choices; ++i) {
1696 extract_token(buf, menustring, i, '|', sizeof buf);
1697 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1698 if ( (ch == tolower(buf[c]))
1700 && (buf[c+1]=='>') ) {
1701 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1702 if ( (a!=(c-1)) && (a!=(c+1))) {