Fix more warnings; output errors to the users.
[citadel.git] / citadel / textclient / commands.c
1 /*
2  * This file contains functions which implement parts of the
3  * text-mode user interface.
4  *
5  * Copyright (c) 1987-2010 by the citadel.org team
6  *
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.
11  *
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.
16  *
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
20  */
21
22 #include "sysdep.h"
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <ctype.h>
28 #include <string.h>
29 #include <sys/types.h>
30
31 #if TIME_WITH_SYS_TIME
32 # include <sys/time.h>
33 # include <time.h>
34 #else
35 # if HAVE_SYS_TIME_H
36 #  include <sys/time.h>
37 # else
38 #  include <time.h>
39 # endif
40 #endif
41
42 #ifdef HAVE_TERMIOS_H
43 #include <termios.h>
44 #else
45 #include <sgtty.h>
46 #endif
47
48 #ifdef HAVE_SYS_SELECT_H
49 #include <sys/select.h>
50 #endif
51
52 #ifdef THREADED_CLIENT
53 #include <pthread.h>
54 #endif
55
56 #include <signal.h>
57 #include <errno.h>
58 #include <stdarg.h>
59 #include <libcitadel.h>
60 #include "citadel.h"
61 #include "citadel_ipc.h"
62 #include "commands.h"
63 #include "messages.h"
64 #include "citadel_decls.h"
65 #include "routines.h"
66 #include "routines2.h"
67 #include "rooms.h"
68 #include "client_chat.h"
69 #include "citadel_dirs.h"
70 #ifndef HAVE_SNPRINTF
71 #include "snprintf.h"
72 #endif
73 #include "screen.h"
74 #include "ecrash.h"
75
76 struct citcmd {
77         struct citcmd *next;
78         int c_cmdnum;
79         int c_axlevel;
80         char c_keys[5][64];
81 };
82
83 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
84
85
86 int rc_exp_beep;
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;
92 int rc_ansi_color;
93 int rc_color_use_bg;
94 int rc_prompt_control = 0;
95 time_t rc_idle_threshold = (time_t)900;
96 char rc_url_cmd[SIZ];
97 char rc_open_cmd[SIZ];
98 char rc_gotmail_cmd[SIZ];
99
100 char *gl_string;
101 int next_lazy_cmd = 5;
102
103 extern int screenwidth, screenheight;
104 extern int termn8;
105 extern CtdlIPC *ipc_for_signal_handlers;        /* KLUDGE cover your eyes */
106
107 struct citcmd *cmdlist = NULL;
108
109
110 /* these variables are local to this module */
111 char keepalives_enabled = KA_YES;       /* send NOOPs to server when idle */
112 int ok_to_interrupt = 0;                /* print instant msgs asynchronously */
113 time_t AnsiDetect;                      /* when did we send the detect code? */
114 int enable_color = 0;                   /* nonzero for ANSI color */
115
116
117
118
119 /*
120  * If an interesting key has been pressed, return its value, otherwise 0
121  */
122 char was_a_key_pressed(void) {
123         fd_set rfds;
124         struct timeval tv;
125         int the_character;
126         int retval;
127
128         FD_ZERO(&rfds);
129         FD_SET(0, &rfds);
130         tv.tv_sec = 0;
131         tv.tv_usec = 0;
132         retval = select(1, &rfds, NULL, NULL, &tv); 
133
134         /* Careful!  Disable keepalives during keyboard polling; we're probably
135          * in the middle of a data transfer from the server, in which case
136          * sending a NOOP would throw the client protocol out of sync.
137          */
138         if ((retval > 0) && FD_ISSET(0, &rfds)) {
139                 set_keepalives(KA_NO);
140                 the_character = inkey();
141                 set_keepalives(KA_YES);
142         }
143         else {
144                 the_character = 0;
145         }
146         return(the_character);
147 }
148
149
150
151
152
153 /*
154  * print_instant()  -  print instant messages if there are any
155  */
156 void print_instant(void)
157 {
158         char buf[1024];
159         FILE *outpipe;
160         time_t timestamp;
161         struct tm stamp;
162         int flags = 0;
163         char sender[64];
164         char node[64];
165         char *listing = NULL;
166         int r;                  /* IPC result code */
167
168         if (instant_msgs == 0)
169                 return;
170
171         if (rc_exp_beep) {
172                 ctdl_beep();
173         }
174         if (IsEmptyStr(rc_exp_cmd)) {
175                 color(BRIGHT_RED);
176                 scr_printf("\r---");
177         }
178         
179         while (instant_msgs != 0) {
180                 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
181                 if (r / 100 != 1)
182                         return;
183         
184                 instant_msgs = extract_int(buf, 0);
185                 timestamp = extract_long(buf, 1);
186                 flags = extract_int(buf, 2);
187                 extract_token(sender, buf, 3, '|', sizeof sender);
188                 extract_token(node, buf, 4, '|', sizeof node);
189                 strcpy(last_paged, sender);
190         
191                 localtime_r(&timestamp, &stamp);
192
193                 /* If the page is a Logoff Request, honor it. */
194                 if (flags & 2) {
195                         termn8 = 1;
196                         return;
197                 }
198         
199                 if (!IsEmptyStr(rc_exp_cmd)) {
200                         outpipe = popen(rc_exp_cmd, "w");
201                         if (outpipe != NULL) {
202                                 /* Header derived from flags */
203                                 if (flags & 2)
204                                         fprintf(outpipe,
205                                                "Please log off now, as requested ");
206                                 else if (flags & 1)
207                                         fprintf(outpipe, "Broadcast message ");
208                                 else if (flags & 4)
209                                         fprintf(outpipe, "Chat request ");
210                                 else
211                                         fprintf(outpipe, "Message ");
212                                 /* Timestamp.  Can this be improved? */
213                                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
214                                         fprintf(outpipe, "at 12:%02d%cm",
215                                                 stamp.tm_min, 
216                                                 stamp.tm_hour ? 'p' : 'a');
217                                 else if (stamp.tm_hour > 12)            /* pm */
218                                         fprintf(outpipe, "at %d:%02dpm",
219                                                 stamp.tm_hour - 12,
220                                                 stamp.tm_min);
221                                 else                                    /* am */
222                                         fprintf(outpipe, "at %d:%02dam",
223                                                 stamp.tm_hour, stamp.tm_min);
224                                 fprintf(outpipe, " from %s", sender);
225                                 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
226                                         fprintf(outpipe, " @%s", node);
227                                 fprintf(outpipe, ":\n%s\n", listing);
228                                 pclose(outpipe);
229                                 if (instant_msgs == 0)
230                                         return;
231                                 continue;
232                         }
233                 }
234                 /* fall back to built-in instant message display */
235                 scr_printf("\n");
236
237                 /* Header derived from flags */
238                 if (flags & 2)
239                         scr_printf("Please log off now, as requested ");
240                 else if (flags & 1)
241                         scr_printf("Broadcast message ");
242                 else if (flags & 4)
243                         scr_printf("Chat request ");
244                 else
245                         scr_printf("Message ");
246         
247                 /* Timestamp.  Can this be improved? */
248                 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
249                         scr_printf("at 12:%02d%cm", stamp.tm_min, 
250                                 stamp.tm_hour ? 'p' : 'a');
251                 else if (stamp.tm_hour > 12)                    /* pm */
252                         scr_printf("at %d:%02dpm",
253                                 stamp.tm_hour - 12, stamp.tm_min);
254                 else                                            /* am */
255                         scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
256                 
257                 /* Sender */
258                 scr_printf(" from %s", sender);
259         
260                 /* Remote node, if any */
261                 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
262                         scr_printf(" @%s", node);
263         
264                 scr_printf(":\n");
265                 fmout(screenwidth, NULL, listing, NULL, 0);
266                 free(listing);
267
268         }
269         scr_printf("\n---\n");
270         color(BRIGHT_WHITE);
271
272
273 }
274
275
276 void set_keepalives(int s)
277 {
278         keepalives_enabled = (char) s;
279 }
280
281 /* 
282  * This loop handles the "keepalive" messages sent to the server when idling.
283  */
284
285 static time_t idlet = 0;
286 static void really_do_keepalive(void) {
287
288         time(&idlet);
289
290         /* This may sometimes get called before we are actually connected
291          * to the server.  Don't do anything if we aren't connected. -IO
292          */
293         if (!ipc_for_signal_handlers)
294                 return;
295
296         /* If full keepalives are enabled, send a NOOP to the server and
297          * wait for a response.
298          */
299         if (keepalives_enabled == KA_YES) {
300                 CtdlIPCNoop(ipc_for_signal_handlers);
301                 if (instant_msgs > 0) {
302                         if (ok_to_interrupt == 1) {
303                                 scr_printf("\r%64s\r", "");
304                                 print_instant();
305                                 scr_printf("%s%c ", room_name,
306                                        room_prompt(room_flags));
307                                 scr_flush();
308                         }
309                 }
310         }
311
312         /* If half keepalives are enabled, send a QNOP to the server (if the
313          * server supports it) and then do nothing.
314          */
315         if ( (keepalives_enabled == KA_HALF)
316            && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
317                 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
318         }
319 }
320
321 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
322    encapsulated interface; in theory there should be no need to touch these
323    globals outside of the async_ka_* functions. */
324
325 #ifdef THREADED_CLIENT
326 static pthread_t ka_thr_handle;
327 static int ka_thr_active = 0;
328 static int async_ka_enabled = 0;
329
330 static void *ka_thread(void *arg)
331 {
332 #ifdef HAVE_BACKTRACE
333         char threadName[256];
334
335         // Set up our name
336         sprintf(threadName, "ka_Thread n");
337
338         // Register for tracing
339         eCrash_RegisterThread(threadName, 0);
340 #endif
341         really_do_keepalive();
342         pthread_detach(ka_thr_handle);
343         ka_thr_active = 0;
344         
345 #ifdef HAVE_BACKTRACE
346         eCrash_UnregisterThread();
347 #endif
348         return NULL;
349 }
350
351 /* start up a thread to handle a keepalive in the background */
352 static void async_ka_exec(void)
353 {
354         if (!ka_thr_active) {
355                 ka_thr_active = 1;
356                 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
357                         perror("pthread_create");
358                         exit(1);
359                 }
360         }
361 }
362 #endif /* THREADED_CLIENT */
363
364 /* I changed this from static to not because I need to call it from
365    screen.c, either that or make something in screen.c not static.
366    Fix it how you like. Why all the staticness? stu */
367    
368 void do_keepalive(void)
369 {
370         time_t now;
371
372         time(&now);
373         if ((now - idlet) < ((long) S_KEEPALIVE))
374                 return;
375
376         /* Do a space-backspace to keep telnet sessions from idling out */
377         scr_printf(" %c", 8);
378         scr_flush();
379
380 #ifdef THREADED_CLIENT
381         if (async_ka_enabled)
382                 async_ka_exec();
383         else
384 #endif
385                 really_do_keepalive();
386 }
387
388
389 /* Now the actual async-keepalve API that we expose to higher levels:
390    async_ka_start() and async_ka_end(). These do nothing when we don't have
391    threading enabled, so we avoid sprinkling ifdef's throughout the code. */
392
393 /* wait for a background keepalive to complete. this must be done before
394    attempting any further server requests! */
395 void async_ka_end(void)
396 {
397 #ifdef THREADED_CLIENT
398         if (ka_thr_active)
399                 pthread_join(ka_thr_handle, NULL);
400
401         async_ka_enabled--;
402 #endif
403 }
404
405 /* tell do_keepalive() that keepalives are asynchronous. */
406 void async_ka_start(void)
407 {
408 #ifdef THREADED_CLIENT
409         async_ka_enabled++;
410 #endif
411 }
412
413
414 int inkey(void)
415 {                               /* get a character from the keyboard, with   */
416         int a;                  /* the watchdog timer in effect if necessary */
417         fd_set rfds;
418         struct timeval tv;
419         time_t start_time;
420
421         scr_flush();
422         time(&start_time);
423
424         do {
425                 /* This loop waits for keyboard input.  If the keepalive
426                  * timer expires, it sends a keepalive to the server if
427                  * necessary and then waits again.
428                  */
429                 do {
430                         do_keepalive();
431
432                         FD_ZERO(&rfds);
433                         FD_SET(0, &rfds);
434                         tv.tv_sec = S_KEEPALIVE;
435                         tv.tv_usec = 0;
436
437                         select(1, &rfds, NULL, NULL, &tv);
438                 } while (!FD_ISSET(0, &rfds));
439
440                 /* At this point, there's input, so fetch it.
441                  * (There's a hole in the bucket...)
442                  */
443                 a = scr_getc(SCR_BLOCK);
444                 if (a == 127) {
445                         a = 8;
446                 }
447                 if (a == 13) {
448                         a = 10;
449                 }
450         } while (a == 0);
451         return (a);
452 }
453
454
455 int yesno(void)
456 {                               /* Returns 1 for yes, 0 for no */
457         int a;
458         while (1) {
459                 a = inkey();
460                 a = tolower(a);
461                 if (a == 'y') {
462                         scr_printf("Yes\n");
463                         return (1);
464                 }
465                 if (a == 'n') {
466                         scr_printf("No\n");
467                         return (0);
468                 }
469         }
470 }
471
472 /* Returns 1 for yes, 0 for no, arg is default value */
473 int yesno_d(int d)
474 {
475         int a;
476         while (1) {
477                 a = inkey();
478                 a = tolower(a);
479                 if (a == 10)
480                         a = (d ? 'y' : 'n');
481                 if (a == 'y') {
482                         scr_printf("Yes\n");
483                         return (1);
484                 }
485                 if (a == 'n') {
486                         scr_printf("No\n");
487                         return (0);
488                 }
489         }
490 }
491
492
493
494
495 /* Gets a line from the terminal */
496 /* string == Pointer to string buffer */
497 /* lim == Maximum length - if negative, no-show */
498 void ctdl_getline(char *string, int lim) 
499 {
500         int a, b;
501         char flag = 0;
502
503         if (lim < 0) {
504                 lim = (0 - lim);
505                 flag = 1;
506         }
507         strcpy(string, "");
508         gl_string = string;
509         async_ka_start();
510
511 GLA:    a = inkey();
512         /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
513         if ((a == 8 || a == 23) && (IsEmptyStr(string)))
514                 goto GLA;
515         if ((a != 10) && (a != 8) && (strlen(string) == lim))
516                 goto GLA;
517         if ((a == 8) && (string[0] != 0)) {
518                 string[strlen(string) - 1] = 0;
519                 scr_putc(8); scr_putc(32); scr_putc(8);
520                 goto GLA;
521         }
522         if ((a == 23) && (string[0] != 0)) {
523                 do {
524                         string[strlen(string) - 1] = 0;
525                         scr_putc(8); scr_putc(32); scr_putc(8);
526                 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
527                 goto GLA;
528         }
529         if ((a == 10)) {
530                 scr_putc(10);
531                 async_ka_end();
532                 return;
533         }
534         if (a < 32)
535                 a = '.';
536         b = strlen(string);
537         string[b] = a;
538         string[b + 1] = 0;
539         if (flag == 0)
540                 scr_putc(a);
541         if (flag == 1)
542                 scr_putc('*');
543         goto GLA;
544 }
545
546
547 /*
548  * strprompt()  -  prompt for a string, print the existing value and
549  *                 allow the user to press return to keep it...
550  */
551 void strprompt(char *prompt, char *str, int len)
552 {
553         int i;
554         char buf[128];
555
556         print_instant();
557         color(DIM_WHITE);
558         scr_printf("%s ", prompt);
559         color(DIM_MAGENTA);
560         scr_printf("[");
561         color(BRIGHT_MAGENTA);
562
563         if (len >= 0) {
564                 scr_printf("%s", str);
565         }
566         else {
567                 for (i=0; !IsEmptyStr(&str[i]); ++i) {
568                         scr_printf("*");
569                 }
570         }
571
572         color(DIM_MAGENTA);
573         scr_printf("]");
574         color(DIM_WHITE);
575         scr_printf(": ");
576         color(BRIGHT_CYAN);
577         ctdl_getline(buf, len);
578         if (buf[0] != 0)
579                 strcpy(str, buf);
580         color(DIM_WHITE);
581 }
582
583 /*
584  * boolprompt()  -  prompt for a yes/no, print the existing value and
585  *                  allow the user to press return to keep it...
586  */
587 int boolprompt(char *prompt, int prev_val)
588 {
589         int r;
590
591         color(DIM_WHITE);
592         scr_printf("%s ", prompt);
593         color(DIM_MAGENTA);
594         scr_printf("[");
595         color(BRIGHT_MAGENTA);
596         scr_printf("%s", (prev_val ? "Yes" : "No"));
597         color(DIM_MAGENTA);
598         scr_printf("]: ");
599         color(BRIGHT_CYAN);
600         r = (yesno_d(prev_val));
601         color(DIM_WHITE);
602         return r;
603 }
604
605 /* 
606  * intprompt()  -  like strprompt(), except for an integer
607  *                 (note that it RETURNS the new value!)
608  */
609 int intprompt(char *prompt, int ival, int imin, int imax)
610 {
611         char buf[16];
612         int i;
613         int p;
614
615         do {
616                 i = ival;
617                 snprintf(buf, sizeof buf, "%d", i);
618                 strprompt(prompt, buf, 15);
619                 i = atoi(buf);
620                 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
621                         if ( (!isdigit(buf[p]))
622                            && ( (buf[p]!='-') || (p!=0) )  )
623                                 i = imin - 1;
624                 }
625                 if (i < imin)
626                         scr_printf("*** Must be no less than %d.\n", imin);
627                 if (i > imax)
628                         scr_printf("*** Must be no more than %d.\n", imax);
629         } while ((i < imin) || (i > imax));
630         return (i);
631 }
632
633 /* 
634  * newprompt()  -  prompt for a string with no existing value
635  *                 (clears out string buffer first)
636  */
637 void newprompt(char *prompt, char *str, int len)
638 {
639         color(BRIGHT_MAGENTA);
640         scr_printf("%s", prompt);
641         color(DIM_MAGENTA);
642         ctdl_getline(str, len);
643         color(DIM_WHITE);
644 }
645
646
647 int lkey(void)
648 {                               /* returns a lower case value */
649         int a;
650         a = inkey();
651         if (isupper(a))
652                 a = tolower(a);
653         return (a);
654 }
655
656 /*
657  * parse the citadel.rc file
658  */
659 void load_command_set(void)
660 {
661         FILE *ccfile;
662         char buf[1024];
663         char editor_key[100];
664         struct citcmd *cptr;
665         struct citcmd *lastcmd = NULL;
666         int a, d;
667         int b = 0;
668         int i;
669
670
671         /* first, set up some defaults for non-required variables */
672
673         for (i = 0; i < MAX_EDITORS; i++)
674                 strcpy(editor_paths[i], "");
675         strcpy(printcmd, "");
676         strcpy(imagecmd, "");
677         strcpy(rc_username, "");
678         strcpy(rc_password, "");
679         rc_floor_mode = 0;
680         rc_exp_beep = 1;
681         rc_allow_attachments = 0;
682         rc_remember_passwords = 0;
683         strcpy(rc_exp_cmd, "");
684         rc_display_message_numbers = 0;
685         rc_force_mail_prompts = 0;
686         rc_ansi_color = 0;
687         rc_color_use_bg = 0;
688         strcpy(rc_url_cmd, "");
689         strcpy(rc_open_cmd, "");
690         strcpy(rc_gotmail_cmd, "");
691 #ifdef HAVE_OPENSSL
692         rc_encrypt = RC_DEFAULT;
693 #endif
694         rc_alt_semantics = 0;
695
696         /* now try to open the citadel.rc file */
697
698         ccfile = NULL;
699         if (getenv("HOME") != NULL) {
700                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
701                 ccfile = fopen(buf, "r");
702         }
703         if (ccfile == NULL) {
704                 ccfile = fopen(file_citadel_rc, "r");
705         }
706         if (ccfile == NULL) {
707                 ccfile = fopen("/etc/citadel.rc", "r");
708         }
709         if (ccfile == NULL) {
710                 ccfile = fopen("./citadel.rc", "r");
711         }
712         if (ccfile == NULL) {
713                 perror("commands: cannot open citadel.rc");
714                 logoff(NULL, 3);
715         }
716         while (fgets(buf, sizeof buf, ccfile) != NULL) {
717                 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
718                         buf[strlen(buf) - 1] = 0;
719
720                 if (!strncasecmp(buf, "encrypt=", 8)) {
721                         if (!strcasecmp(&buf[8], "yes")) {
722 #ifdef HAVE_OPENSSL
723                                 rc_encrypt = RC_YES;
724 #else
725                                 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
726                                 logoff(NULL, 3);
727 #endif
728                         }
729 #ifdef HAVE_OPENSSL
730                         else if (!strcasecmp(&buf[8], "no")) {
731                                 rc_encrypt = RC_NO;
732                         }
733                         else if (!strcasecmp(&buf[8], "default")) {
734                                 rc_encrypt = RC_DEFAULT;
735                         }
736 #endif
737                 }
738
739                 if (!strncasecmp(buf, "editor=", 7))
740                         strcpy(editor_paths[0], &buf[7]);
741
742                 for (i = 0; i < MAX_EDITORS; i++)
743                 {
744                         sprintf(editor_key, "editor%d=", i);
745                         if (!strncasecmp(buf, editor_key, strlen(editor_key)))
746                                 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
747                 }
748
749                 if (!strncasecmp(buf, "printcmd=", 9))
750                         strcpy(printcmd, &buf[9]);
751
752                 if (!strncasecmp(buf, "imagecmd=", 9))
753                         strcpy(imagecmd, &buf[9]);
754
755                 if (!strncasecmp(buf, "expcmd=", 7))
756                         strcpy(rc_exp_cmd, &buf[7]);
757
758                 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
759                         have_xterm = (char) atoi(&buf[24]);
760
761                 if (!strncasecmp(buf, "use_floors=", 11)) {
762                         if (!strcasecmp(&buf[11], "yes"))
763                                 rc_floor_mode = RC_YES;
764                         if (!strcasecmp(&buf[11], "no"))
765                                 rc_floor_mode = RC_NO;
766                         if (!strcasecmp(&buf[11], "default"))
767                                 rc_floor_mode = RC_DEFAULT;
768                 }
769                 if (!strncasecmp(buf, "beep=", 5)) {
770                         rc_exp_beep = atoi(&buf[5]);
771                 }
772                 if (!strncasecmp(buf, "allow_attachments=", 18)) {
773                         rc_allow_attachments = atoi(&buf[18]);
774                 }
775                 if (!strncasecmp(buf, "idle_threshold=", 15)) {
776                         rc_idle_threshold = atol(&buf[15]);
777                 }
778                 if (!strncasecmp(buf, "remember_passwords=", 19)) {
779                         rc_remember_passwords = atoi(&buf[19]);
780                 }
781                 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
782                         rc_display_message_numbers = atoi(&buf[24]);
783                 }
784                 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
785                         rc_force_mail_prompts = atoi(&buf[19]);
786                 }
787                 if (!strncasecmp(buf, "ansi_color=", 11)) {
788                         if (!strncasecmp(&buf[11], "on", 2))
789                                 rc_ansi_color = 1;
790                         if (!strncasecmp(&buf[11], "auto", 4))
791                                 rc_ansi_color = 2;      /* autodetect */
792                         if (!strncasecmp(&buf[11], "user", 4))
793                                 rc_ansi_color = 3;      /* user config */
794                 }
795                 if (!strncasecmp(buf, "use_background=", 15)) {
796                         if (!strncasecmp(&buf[15], "on", 2))
797                                 rc_color_use_bg = 9;
798                 }
799                 if (!strncasecmp(buf, "prompt_control=", 15)) {
800                         if (!strncasecmp(&buf[15], "on", 2))
801                                 rc_prompt_control = 1;
802                         if (!strncasecmp(&buf[15], "user", 4))
803                                 rc_prompt_control = 3;  /* user config */
804                 }
805                 if (!strncasecmp(buf, "username=", 9))
806                         strcpy(rc_username, &buf[9]);
807
808                 if (!strncasecmp(buf, "password=", 9))
809                         strcpy(rc_password, &buf[9]);
810
811                 if (!strncasecmp(buf, "urlcmd=", 7))
812                         strcpy(rc_url_cmd, &buf[7]);
813
814                 if (!strncasecmp(buf, "opencmd=", 7))
815                         strcpy(rc_open_cmd, &buf[8]);
816
817                 if (!strncasecmp(buf, "gotmailcmd=", 11))
818                         strcpy(rc_gotmail_cmd, &buf[11]);
819
820                 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
821                         if (!strncasecmp(&buf[20], "yes", 3)) {
822                                 rc_alt_semantics = 1;
823                         }
824                         else {
825                                 rc_alt_semantics = 0;
826                         }
827                 }
828
829                 if (!strncasecmp(buf, "cmd=", 4)) {
830                         strcpy(buf, &buf[4]);
831
832                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
833
834                         cptr->c_cmdnum = atoi(buf);
835                         for (d = strlen(buf); d >= 0; --d)
836                                 if (buf[d] == ',')
837                                         b = d;
838                         strcpy(buf, &buf[b + 1]);
839
840                         cptr->c_axlevel = atoi(buf);
841                         for (d = strlen(buf); d >= 0; --d)
842                                 if (buf[d] == ',')
843                                         b = d;
844                         strcpy(buf, &buf[b + 1]);
845
846                         for (a = 0; a < 5; ++a)
847                                 cptr->c_keys[a][0] = 0;
848
849                         a = 0;
850                         b = 0;
851                         buf[strlen(buf) + 1] = 0;
852                         while (!IsEmptyStr(buf)) {
853                                 b = strlen(buf);
854                                 for (d = strlen(buf); d >= 0; --d)
855                                         if (buf[d] == ',')
856                                                 b = d;
857                                 strncpy(cptr->c_keys[a], buf, b);
858                                 cptr->c_keys[a][b] = 0;
859                                 if (buf[b] == ',')
860                                         strcpy(buf, &buf[b + 1]);
861                                 else
862                                         strcpy(buf, "");
863                                 ++a;
864                         }
865
866                         cptr->next = NULL;
867                         if (cmdlist == NULL)
868                                 cmdlist = cptr;
869                         else
870                                 lastcmd->next = cptr;
871                         lastcmd = cptr;
872                 }
873         }
874         fclose(ccfile);
875 }
876
877
878
879 /*
880  * return the key associated with a command
881  */
882 char keycmd(char *cmdstr)
883 {
884         int a;
885
886         for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
887                 if (cmdstr[a] == '&')
888                         return (tolower(cmdstr[a + 1]));
889         return (0);
890 }
891
892
893 /*
894  * Output the string from a key command without the ampersand
895  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
896  */
897 char *cmd_expand(char *strbuf, int mode)
898 {
899         int a;
900         static char exp[64];
901         char buf[1024];
902
903         strcpy(exp, strbuf);
904
905         for (a = 0; exp[a]; ++a) {
906                 if (strbuf[a] == '&') {
907
908                         /* dont echo these non mnemonic command keys */
909                         int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
910
911                         if (mode == 0) {
912                                 strcpy(&exp[a], &exp[a + 1 + noecho]);
913                         }
914                         if (mode == 1) {
915                                 exp[a] = '<';
916                                 strcpy(buf, &exp[a + 2]);
917                                 exp[a + 2] = '>';
918                                 exp[a + 3] = 0;
919                                 strcat(exp, buf);
920                         }
921                 }
922                 if (!strncmp(&exp[a], "^r", 2)) {
923                         strcpy(buf, exp);
924                         strcpy(&exp[a], room_name);
925                         strcat(exp, &buf[a + 2]);
926                 }
927                 if (!strncmp(&exp[a], "^c", 2)) {
928                         exp[a] = ',';
929                         strcpy(&exp[a + 1], &exp[a + 2]);
930                 }
931         }
932
933         return (exp);
934 }
935
936
937
938 /*
939  * Comparison function to determine if entered commands match a
940  * command loaded from the config file.
941  */
942 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
943 {
944         int a;
945         int cmdax;
946
947         cmdax = 0;
948         if (is_room_aide)
949                 cmdax = 1;
950         if (axlevel >= 6)
951                 cmdax = 2;
952
953         for (a = 0; a < ncomp; ++a) {
954                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
955                     || (cptr->c_axlevel > cmdax))
956                         return (0);
957         }
958         return (1);
959 }
960
961
962 /*
963  * This function returns 1 if a given command requires a string input
964  */
965 int requires_string(struct citcmd *cptr, int ncomp)
966 {
967         int a;
968         char buf[64];
969
970         strcpy(buf, cptr->c_keys[ncomp - 1]);
971         for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
972                 if (buf[a] == ':')
973                         return (1);
974         }
975         return (0);
976 }
977
978
979 /*
980  * Input a command at the main prompt.
981  * This function returns an integer command number.  If the command prompts
982  * for a string then it is placed in the supplied buffer.
983  */
984 int getcmd(CtdlIPC *ipc, char *argbuf)
985 {
986         char cmdbuf[5];
987         int cmdspaces[5];
988         int cmdpos;
989         int ch;
990         int a;
991         int got;
992         int this_lazy_cmd;
993         struct citcmd *cptr;
994
995         /*
996          * Starting a new command now, so set sigcaught to 0.  This variable
997          * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
998          * been interrupted by a keypress.
999          */
1000         sigcaught = 0;
1001
1002         /* Switch color support on or off if we're in user mode */
1003         if (rc_ansi_color == 3) {
1004                 if (userflags & US_COLOR)
1005                         enable_color = 1;
1006                 else
1007                         enable_color = 0;
1008         }
1009         /* if we're running in idiot mode, display a cute little menu */
1010         IFNEXPERT formout(ipc, "mainmenu");
1011
1012         print_instant();
1013         strcpy(argbuf, "");
1014         cmdpos = 0;
1015         for (a = 0; a < 5; ++a)
1016                 cmdbuf[a] = 0;
1017         /* now the room prompt... */
1018         ok_to_interrupt = 1;
1019         color(BRIGHT_WHITE);
1020         scr_printf("\n%s", room_name);
1021         color(DIM_WHITE);
1022         scr_printf("%c ", room_prompt(room_flags));
1023
1024         while (1) {
1025                 ch = inkey();
1026                 ok_to_interrupt = 0;
1027
1028                 /* Handle the backspace key, but only if there's something
1029                  * to backspace over...
1030                  */
1031                 if ((ch == 8) && (cmdpos > 0)) {
1032                         back(cmdspaces[cmdpos - 1] + 1);
1033                         cmdbuf[cmdpos] = 0;
1034                         --cmdpos;
1035                 }
1036                 /* Spacebar invokes "lazy traversal" commands */
1037                 if ((ch == 32) && (cmdpos == 0)) {
1038                         this_lazy_cmd = next_lazy_cmd;
1039                         if (this_lazy_cmd == 13)
1040                                 next_lazy_cmd = 5;
1041                         if (this_lazy_cmd == 5)
1042                                 next_lazy_cmd = 13;
1043                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1044                                 if (cptr->c_cmdnum == this_lazy_cmd) {
1045                                         for (a = 0; a < 5; ++a)
1046                                                 if (cptr->c_keys[a][0] != 0)
1047                                                         scr_printf("%s ", cmd_expand(
1048                                                                                         cptr->c_keys[a], 0));
1049                                         scr_printf("\n");
1050                                         return (this_lazy_cmd);
1051                                 }
1052                         }
1053                         scr_printf("\n");
1054                         return (this_lazy_cmd);
1055                 }
1056                 /* Otherwise, process the command */
1057                 cmdbuf[cmdpos] = tolower(ch);
1058
1059                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1060                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1061
1062                                 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1063                                 cmdspaces[cmdpos] = strlen(
1064                                     cmd_expand(cptr->c_keys[cmdpos], 0));
1065                                 if (cmdpos < 4)
1066                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
1067                                                 scr_putc(' ');
1068                                 ++cmdpos;
1069                         }
1070                 }
1071
1072                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1073                         if (cmdmatch(cmdbuf, cptr, 5)) {
1074                                 /* We've found our command. */
1075                                 if (requires_string(cptr, cmdpos)) {
1076                                         ctdl_getline(argbuf, 64);
1077                                 } else {
1078                                         scr_printf("\n");
1079                                 }
1080
1081                                 /* If this command is one that changes rooms,
1082                                  * then the next lazy-command (space bar)
1083                                  * should be "read new" instead of "goto"
1084                                  */
1085                                 if ((cptr->c_cmdnum == 5)
1086                                     || (cptr->c_cmdnum == 6)
1087                                     || (cptr->c_cmdnum == 47)
1088                                     || (cptr->c_cmdnum == 52)
1089                                     || (cptr->c_cmdnum == 16)
1090                                     || (cptr->c_cmdnum == 20))
1091                                         next_lazy_cmd = 13;
1092
1093                                 /* If this command is "read new"
1094                                  * then the next lazy-command (space bar)
1095                                  * should be "goto"
1096                                  */
1097                                 if (cptr->c_cmdnum == 13)
1098                                         next_lazy_cmd = 5;
1099
1100                                 return (cptr->c_cmdnum);
1101
1102                         }
1103                 }
1104
1105                 if (ch == '?') {
1106                         scr_printf("\rOne of ...                         \n");
1107                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1108                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1109                                         for (a = 0; a < 5; ++a) {
1110                                            keyopt(cmd_expand(cptr->c_keys[a], 1));
1111                                    scr_printf(" ");
1112                                         }
1113                                         scr_printf("\n");
1114                                 }
1115                         }
1116                 sigcaught = 0;
1117
1118                         scr_printf("\n%s%c ", room_name, room_prompt(room_flags));
1119                         got = 0;
1120                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1121                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1122                                         for (a = 0; a < cmdpos; ++a) {
1123                                                 scr_printf("%s ",
1124                                                        cmd_expand(cptr->c_keys[a], 0));
1125                                         }
1126                                         got = 1;
1127                                 }
1128                         }
1129                 }
1130         }
1131
1132 }
1133
1134
1135
1136
1137
1138 /*
1139  * set tty modes.  commands are:
1140  * 
1141  * 01- set to Citadel mode
1142  * 2 - save current settings for later restoral
1143  * 3 - restore saved settings
1144  */
1145 #ifdef HAVE_TERMIOS_H
1146 void stty_ctdl(int cmd)
1147 {                               /* SysV version of stty_ctdl() */
1148         struct termios live;
1149         static struct termios saved_settings;
1150         static int last_cmd = 0;
1151
1152         if (cmd == SB_LAST)
1153                 cmd = last_cmd;
1154         else
1155                 last_cmd = cmd;
1156
1157         if ((cmd == 0) || (cmd == 1)) {
1158                 tcgetattr(0, &live);
1159                 live.c_iflag = ISTRIP | IXON | IXANY;
1160                 live.c_oflag = OPOST | ONLCR;
1161                 live.c_lflag = ISIG | NOFLSH;
1162
1163                 live.c_cc[VINTR] = 0;
1164                 live.c_cc[VQUIT] = 0;
1165
1166 #ifdef hpux
1167                 live.c_cc[VMIN] = 0;
1168                 live.c_cc[VTIME] = 0;
1169 #endif
1170
1171                 /* do we even need this stuff anymore? */
1172                 /* live.c_line=0; */
1173                 live.c_cc[VERASE] = 8;
1174                 live.c_cc[VKILL] = 24;
1175                 live.c_cc[VEOF] = 1;
1176                 live.c_cc[VEOL] = 255;
1177                 live.c_cc[VEOL2] = 0;
1178                 live.c_cc[VSTART] = 0;
1179                 tcsetattr(0, TCSADRAIN, &live);
1180         }
1181         if (cmd == 2) {
1182                 tcgetattr(0, &saved_settings);
1183         }
1184         if (cmd == 3) {
1185                 tcsetattr(0, TCSADRAIN, &saved_settings);
1186         }
1187
1188 }
1189 #else
1190 void stty_ctdl(int cmd)
1191 {                               /* BSD version of stty_ctdl() */
1192         struct sgttyb live;
1193         static struct sgttyb saved_settings;
1194         static int last_cmd = 0;
1195
1196         if (cmd == SB_LAST)
1197                 cmd = last_cmd;
1198         else
1199                 last_cmd = cmd;
1200
1201         if ((cmd == 0) || (cmd == 1)) {
1202                 gtty(0, &live);
1203                 live.sg_flags |= CBREAK;
1204                 live.sg_flags |= CRMOD;
1205                 live.sg_flags |= NL1;
1206                 live.sg_flags &= ~ECHO;
1207                 if (cmd == 1)
1208                         live.sg_flags |= NOFLSH;
1209                 stty(0, &live);
1210         }
1211         if (cmd == 2) {
1212                 gtty(0, &saved_settings);
1213         }
1214         if (cmd == 3) {
1215                 stty(0, &saved_settings);
1216         }
1217 }
1218 #endif
1219
1220
1221 /*
1222  * display_help()  -  help file viewer
1223  */
1224 void display_help(CtdlIPC *ipc, char *name)
1225 {
1226         formout(ipc, name);
1227 }
1228
1229
1230 /*
1231  * fmout() - Citadel text formatter and paginator
1232  */
1233 int fmout(
1234         int width,      /* screen width to use */
1235         FILE *fpin,     /* file to read from, or NULL to format given text */
1236         char *text,     /* text to be formatted (when fpin is NULL */
1237         FILE *fpout,    /* file to write to, or NULL to write to screen */
1238         int subst)      /* nonzero if we should use hypertext mode */
1239 {
1240         char *buffer = NULL;    /* The current message */
1241         char *word = NULL;      /* What we are about to actually print */
1242         char *e;                /* Pointer to position in text */
1243         char old = 0;           /* The previous character */
1244         int column = 0;         /* Current column */
1245         size_t i;               /* Generic counter */
1246
1247         /* Space for a single word, which can be at most screenwidth */
1248         word = (char *)calloc(1, width);
1249         if (!word) {
1250                 scr_printf("Can't alloc memory to print message: %s!\n",
1251                                 strerror(errno));
1252                 logoff(NULL, 3);
1253         }
1254
1255         /* Read the entire message body into memory */
1256         if (fpin) {
1257                 buffer = load_message_from_file(fpin);
1258                 if (!buffer) {
1259                         scr_printf("Can't print message: %s!\n",
1260                                         strerror(errno));
1261                         logoff(NULL, 3);
1262                 }
1263         } else {
1264                 buffer = text;
1265         }
1266         e = buffer;
1267
1268         /* Run the message body */
1269         while (*e) {
1270                 /* Catch characters that shouldn't be there at all */
1271                 if (*e == '\r') {
1272                         e++;
1273                         continue;
1274                 }
1275                 /* First, are we looking at a newline? */
1276                 if (*e == '\n') {
1277                         e++;
1278                         if (*e == ' ') {        /* Paragraph */
1279                                 if (fpout) {
1280                                         fprintf(fpout, "\n");
1281                                 } else {
1282                                         scr_printf("\n");
1283                                 }
1284                                 column = 0;
1285                         } else if (old != ' ') {/* Don't print two spaces */
1286                                 if (fpout) {
1287                                         fprintf(fpout, " ");
1288                                 } else {
1289                                         scr_printf(" ");
1290                                 }
1291                                 column++;
1292                         }
1293                         old = '\n';
1294                         continue;
1295                 }
1296
1297                 /* Are we looking at a nonprintable?
1298                  * (This section is now commented out because we could be displaying
1299                  * a character set like UTF-8 or ISO-8859-1.)
1300                 if ( (*e < 32) || (*e > 126) ) {
1301                         e++;
1302                         continue;
1303                 } */
1304
1305                 /* Or are we looking at a space? */
1306                 if (*e == ' ') {
1307                         e++;
1308                         if (column >= width - 1) {
1309                                 /* Are we in the rightmost column? */
1310                                 if (fpout) {
1311                                         fprintf(fpout, "\n");
1312                                 } else {
1313                                         scr_printf("\n");
1314                                 }
1315                                 column = 0;
1316                         } else if (!(column == 0 && old == ' ')) {
1317                                 /* Eat only the first space on a line */
1318                                 if (fpout) {
1319                                         fprintf(fpout, " ");
1320                                 } else {
1321                                         scr_printf(" ");
1322                                 }
1323                                 column++;
1324                         }
1325                         /* ONLY eat the FIRST space on a line */
1326                         old = ' ';
1327                         continue;
1328                 }
1329                 old = *e;
1330
1331                 /* Read a word, slightly messy */
1332                 i = 0;
1333                 while (e[i]) {
1334                         if (!isprint(e[i]) && !isspace(e[i]))
1335                                 e[i] = ' ';
1336                         if (isspace(e[i]))
1337                                 break;
1338                         i++;
1339                 }
1340
1341                 /* We should never see these, but... slightly messy */
1342                 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1343                         e[i] = ' ';
1344
1345                 /* Break up really long words */
1346                 /* TODO: auto-hyphenation someday? */
1347                 if (i >= width) 
1348                         i = width - 1;
1349                 strncpy(word, e, i);
1350                 word[i] = 0;
1351
1352                 /* Decide where to print the word */
1353                 if (column + i >= width) {
1354                         /* Wrap to the next line */
1355                         if (fpout) {
1356                                 fprintf(fpout, "\n");
1357                         } else {
1358                                 scr_printf("\n");
1359                         }
1360                         column = 0;
1361                 }
1362
1363                 /* Print the word */
1364                 if (fpout) {
1365                         fprintf(fpout, "%s", word);
1366                 } else {
1367                         scr_printf("%s", word);
1368                 }
1369                 column += i;
1370                 e += i;         /* Start over with the whitepsace! */
1371         }
1372
1373         free(word);
1374         if (fpin)               /* We allocated this, remember? */
1375                 free(buffer);
1376
1377         /* Is this necessary?  It makes the output kind of spacey. */
1378         if (fpout) {
1379                 fprintf(fpout, "\n");
1380         } else {
1381                 scr_printf("\n");
1382         }
1383
1384         return sigcaught;
1385 }
1386
1387
1388 /*
1389  * support ANSI color if defined
1390  */
1391 void color(int colornum)
1392 {
1393         static int hold_color;
1394         static int current_color;
1395
1396         if (colornum == COLOR_PUSH) {
1397                 hold_color = current_color;
1398                 return;
1399         }
1400
1401         if (colornum == COLOR_POP) {
1402                 color(hold_color);
1403                 return;
1404         }
1405
1406         current_color = colornum;
1407         if (enable_color) {
1408                 /* When switching to dim white, actually output an 'original
1409                  * pair' sequence -- this looks better on black-on-white
1410                  * terminals. - Changed to ORIGINAL_PAIR as this actually
1411                  * wound up looking horrible on black-on-white terminals, not
1412                  * to mention transparent terminals.
1413                  */
1414                 if (colornum == ORIGINAL_PAIR)
1415                         printf("\033[0;39;49m");
1416                 else
1417                         printf("\033[%d;3%d;4%dm", 
1418                                         (colornum & 8) ? 1 : 0,
1419                                         (colornum & 7),
1420                                         rc_color_use_bg);
1421
1422         }
1423 }
1424
1425 void cls(int colornum)
1426 {
1427         if (enable_color) {
1428                 printf("\033[4%dm\033[2J\033[H\033[0m",
1429                                 colornum ? colornum : rc_color_use_bg);
1430         }
1431 }
1432
1433
1434 /*
1435  * Detect whether ANSI color is available (answerback)
1436  */
1437 void send_ansi_detect(void)
1438 {
1439         if (rc_ansi_color == 2) {
1440                 printf("\033[c");
1441                 scr_flush();
1442                 time(&AnsiDetect);
1443         }
1444 }
1445
1446 void look_for_ansi(void)
1447 {
1448         fd_set rfds;
1449         struct timeval tv;
1450         char abuf[512];
1451         time_t now;
1452         int a, rv;
1453
1454         if (rc_ansi_color == 0) {
1455                 enable_color = 0;
1456         } else if (rc_ansi_color == 1) {
1457                 enable_color = 1;
1458         } else if (rc_ansi_color == 2) {
1459
1460                 /* otherwise, do the auto-detect */
1461
1462                 strcpy(abuf, "");
1463
1464                 time(&now);
1465                 if ((now - AnsiDetect) < 2)
1466                         sleep(1);
1467
1468                 do {
1469                         FD_ZERO(&rfds);
1470                         FD_SET(0, &rfds);
1471                         tv.tv_sec = 0;
1472                         tv.tv_usec = 1;
1473
1474                         select(1, &rfds, NULL, NULL, &tv);
1475                         if (FD_ISSET(0, &rfds)) {
1476                                 abuf[strlen(abuf) + 1] = 0;
1477                                 rv = read(0, &abuf[strlen(abuf)], 1);
1478                                 if (rv < 0) {
1479                                         scr_printf("failed to read after select: %s", 
1480                                                    strerror(errno));
1481                                         break;
1482                                 }
1483                         }
1484                 } while (FD_ISSET(0, &rfds));
1485
1486                 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1487                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1488                             && (abuf[a + 2] == '?')) {
1489                                 enable_color = 1;
1490                         }
1491                 }
1492         }
1493 }
1494
1495
1496 /*
1497  * Display key options (highlight hotkeys inside angle brackets)
1498  */
1499 void keyopt(char *buf) {
1500         int i;
1501
1502         color(DIM_WHITE);
1503         for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1504                 if (buf[i]=='<') {
1505                         scr_printf("%c", buf[i]);
1506                         color(BRIGHT_MAGENTA);
1507                 } else {
1508                         if (buf[i]=='>'&& buf[i+1] != '>') {
1509                                 color(DIM_WHITE);
1510                         }
1511                         scr_printf("%c", buf[i]);
1512                 }
1513         }
1514         color(DIM_WHITE);
1515 }
1516
1517
1518
1519 /*
1520  * Present a key-menu line choice type of thing
1521  */
1522 char keymenu(char *menuprompt, char *menustring) {
1523         int i, c, a;
1524         int choices;
1525         int do_prompt = 0;
1526         char buf[1024];
1527         int ch;
1528         int display_prompt = 1;
1529
1530         choices = num_tokens(menustring, '|');
1531
1532         if (menuprompt != NULL) do_prompt = 1;
1533         if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1534
1535         while (1) {
1536                 if (display_prompt) {
1537                         if (do_prompt) {
1538                                 scr_printf("%s ", menuprompt);
1539                         } 
1540                         else {
1541                                 for (i=0; i<choices; ++i) {
1542                                         extract_token(buf, menustring, i, '|', sizeof buf);
1543                                         keyopt(buf);
1544                                         scr_printf(" ");
1545                                 }
1546                         }
1547                         scr_printf("-> ");
1548                         display_prompt = 0;
1549                 }
1550                 ch = lkey();
1551         
1552                 if ( (do_prompt) && (ch=='?') ) {
1553                         scr_printf("\rOne of...                               ");
1554                         scr_printf("                                      \n");
1555                         for (i=0; i<choices; ++i) {
1556                                 extract_token(buf, menustring, i, '|', sizeof buf);
1557                                 scr_printf("   ");
1558                                 keyopt(buf);
1559                                 scr_printf("\n");
1560                         }
1561                         scr_printf("\n");
1562                         display_prompt = 1;
1563                 }
1564
1565                 for (i=0; i<choices; ++i) {
1566                         extract_token(buf, menustring, i, '|', sizeof buf);
1567                         for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1568                                 if ( (ch == tolower(buf[c]))
1569                                    && (buf[c-1]=='<')
1570                                    && (buf[c+1]=='>') ) {
1571                                         for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1572                                                 if ( (a!=(c-1)) && (a!=(c+1))) {
1573                                                         scr_putc(buf[a]);
1574                                                 }
1575                                         }
1576                                         scr_printf("\n");
1577                                         return ch;
1578                                 }
1579                         }
1580                 }
1581         }
1582 }