* Fixed a bug in keyboard polling (in commands.c) which was causing the
[citadel.git] / citadel / commands.c
1 /*
2  * $Id$
3  *
4  * This file contains functions which implement parts of the
5  * text-mode user interface.
6  *
7  */
8
9 #include "sysdep.h"
10 #include <stdlib.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <fcntl.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <sys/types.h>
17 #include <sys/time.h>
18
19 #ifdef HAVE_TERMIOS_H
20 #include <termios.h>
21 #else
22 #include <sgtty.h>
23 #endif
24
25 #ifdef HAVE_SYS_SELECT_H
26 #include <sys/select.h>
27 #endif
28
29 #ifdef THREADED_CLIENT
30 #include <pthread.h>
31 #endif
32
33 #include <signal.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include "citadel.h"
37 #include "commands.h"
38 #include "messages.h"
39 #include "citadel_decls.h"
40 #include "routines.h"
41 #include "routines2.h"
42 #include "tools.h"
43 #include "rooms.h"
44 #ifndef HAVE_SNPRINTF
45 #include "snprintf.h"
46 #endif
47
48 struct citcmd {
49         struct citcmd *next;
50         int c_cmdnum;
51         int c_axlevel;
52         char c_keys[5][64];
53 };
54
55 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
56
57
58 int rc_exp_beep;
59 char rc_exp_cmd[256];
60 int rc_allow_attachments;
61 int rc_display_message_numbers;
62 int rc_force_mail_prompts;
63 int rc_remember_passwords;
64 int rc_ansi_color;
65 int num_urls = 0;
66 char urls[MAXURLS][256];
67 char rc_url_cmd[256];
68
69 char *gl_string;
70 int next_lazy_cmd = 5;
71
72 int lines_printed = 0;          /* line count for paginator */
73 extern int screenwidth, screenheight;
74
75 struct citcmd *cmdlist = NULL;
76
77
78 /* these variables are local to this module */
79 char keepalives_enabled = KA_YES;       /* send NOOPs to server when idle */
80 int ok_to_interrupt = 0;                /* print express msgs asynchronously */
81 time_t AnsiDetect;                      /* when did we send the detect code? */
82 int enable_color = 0;                   /* nonzero for ANSI color */
83
84
85
86
87 /*
88  * If an interesting key has been pressed, return its value, otherwise 0
89  */
90 char was_a_key_pressed(void) {
91         fd_set rfds;
92         struct timeval tv;
93         int the_character;
94         int retval;
95
96         FD_ZERO(&rfds);
97         FD_SET(0, &rfds);
98         tv.tv_sec = 0;
99         tv.tv_usec = 0;
100         retval = select(1, &rfds, NULL, NULL, &tv); 
101
102         /* Careful!  Disable keepalives during keyboard polling; we're probably
103          * in the middle of a data transfer from the server, in which case
104          * sending a NOOP would throw the client protocol out of sync.
105          */
106         if (FD_ISSET(0, &rfds)) {
107                 set_keepalives(KA_NO);
108                 the_character = inkey();
109                 set_keepalives(KA_YES);
110         }
111         else {
112                 the_character = 0;
113         }
114         return(the_character);
115 }
116
117
118
119
120
121 /*
122  * Check to see if we need to pause at the end of a screen.
123  * If we do, we have to disable server keepalives during the pause because
124  * we are probably in the middle of a server operation and the NOOP command
125  * would confuse everything.
126  */
127 int checkpagin(int lp, int pagin, int height)
128 {
129         int thekey;
130
131         if (sigcaught) return(lp);
132         thekey = was_a_key_pressed();
133         if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
134         if (sigcaught) return(lp);
135
136         if (!pagin) return(0);
137         if (lp>=(height-1)) {
138                 set_keepalives(KA_NO);
139                 hit_any_key();
140                 set_keepalives(KA_YES);
141                 return(0);
142         }
143         return(lp);
144 }
145
146
147
148
149 /*
150  * pprintf()  ...   paginated version of printf()
151  */
152 void pprintf(const char *format, ...) {   
153         va_list arg_ptr;
154         static char buf[4096];  /* static for performance, change if needed */
155         int i;
156
157         /* If sigcaught is nonzero, a keypress has interrupted this and we
158          * should just drain output.
159          */
160         if (sigcaught) return;
161  
162         /* Otherwise, start spewing... */ 
163         va_start(arg_ptr, format);   
164         vsprintf(buf, format, arg_ptr);   
165         va_end(arg_ptr);   
166
167         for (i=0; i<strlen(buf); ++i) {
168                 putc(buf[i], stdout);
169                 if (buf[i]==10) {
170                         ++lines_printed;
171                         lines_printed = checkpagin(lines_printed,
172                                 (userflags & US_PAGINATOR),
173                                 screenheight);
174                 }
175         }
176 }   
177
178
179
180 /*
181  * print_express()  -  print express messages if there are any
182  */
183 void print_express(void)
184 {
185         char buf[256];
186         FILE *outpipe;
187
188         if (express_msgs == 0)
189                 return;
190         express_msgs = 0;
191         serv_puts("PEXP");
192         serv_gets(buf);
193         if (buf[0] != '1')
194                 return;
195
196         if (strlen(rc_exp_cmd) > 0) {
197                 outpipe = popen(rc_exp_cmd, "w");
198                 if (outpipe != NULL) {
199                         while (serv_gets(buf), strcmp(buf, "000")) {
200                                 fprintf(outpipe, "%s\n", buf);
201                         }
202                         pclose(outpipe);
203                         return;
204                 }
205         }
206         /* fall back to built-in express message display */
207         if (rc_exp_beep) {
208                 putc(7, stdout);
209         }
210         color(BRIGHT_RED);
211         printf("\r---\n");
212         while (serv_gets(buf), strcmp(buf, "000")) {
213                 printf("%s\n", buf);
214         }
215         printf("---\n");
216         color(BRIGHT_WHITE);
217 }
218
219
220 void set_keepalives(int s)
221 {
222         keepalives_enabled = (char) s;
223 }
224
225 /* 
226  * This loop handles the "keepalive" messages sent to the server when idling.
227  */
228
229 static time_t idlet = 0;
230 static void really_do_keepalive(void) {
231         char buf[256];
232
233         time(&idlet);
234         if (keepalives_enabled == KA_YES) {
235                 serv_puts("NOOP");
236                 serv_gets(buf);
237                 if (buf[3] == '*') {
238                         express_msgs = 1;
239                         if (ok_to_interrupt == 1) {
240                                 printf("\r%64s\r", "");
241                                 print_express();
242                                 printf("%s%c ", room_name,
243                                        room_prompt(room_flags));
244                                 fflush(stdout);
245                         }
246                 }
247         }
248 }
249
250 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
251    encapsulated interface; in theory there should be no need to touch these
252    globals outside of the async_ka_* functions. */
253
254 #ifdef THREADED_CLIENT
255 static pthread_t ka_thr_handle;
256 static int ka_thr_active = 0;
257 static int async_ka_enabled = 0;
258
259 static void *ka_thread(void *arg)
260 {
261         really_do_keepalive();
262         pthread_detach(ka_thr_handle);
263         ka_thr_active = 0;
264         return NULL;
265 }
266
267 /* start up a thread to handle a keepalive in the background */
268 static void async_ka_exec(void)
269 {
270         if (!ka_thr_active) {
271                 ka_thr_active = 1;
272                 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
273                         perror("pthread_create");
274                         exit(1);
275                 }
276         }
277 }
278 #endif /* THREADED_CLIENT */
279
280 static void do_keepalive(void)
281 {
282         time_t now;
283
284         time(&now);
285         if ((now - idlet) < ((long) S_KEEPALIVE))
286                 return;
287
288         /* Do a space-backspace to keep telnet sessions from idling out */
289         printf(" %c", 8);
290         fflush(stdout);
291
292 #ifdef THREADED_CLIENT
293         if (async_ka_enabled)
294                 async_ka_exec();
295         else
296 #endif
297                 really_do_keepalive();
298 }
299
300
301 /* Now the actual async-keepalve API that we expose to higher levels:
302    async_ka_start() and async_ka_end(). These do nothing when we don't have
303    threading enabled, so we avoid sprinkling ifdef's throughout the code. */
304
305 /* wait for a background keepalive to complete. this must be done before
306    attempting any further server requests! */
307 void async_ka_end(void)
308 {
309 #ifdef THREADED_CLIENT
310         if (ka_thr_active)
311                 pthread_join(ka_thr_handle, NULL);
312
313         async_ka_enabled--;
314 #endif
315 }
316
317 /* tell do_keepalive() that keepalives are asynchronous. */
318 void async_ka_start(void)
319 {
320 #ifdef THREADED_CLIENT
321         async_ka_enabled++;
322 #endif
323 }
324
325
326 int inkey(void)
327 {                               /* get a character from the keyboard, with   */
328         int a;                  /* the watchdog timer in effect if necessary */
329         fd_set rfds;
330         struct timeval tv;
331         time_t start_time, now;
332         char inbuf[2];
333
334         fflush(stdout);
335         lines_printed = 0;
336         time(&start_time);
337
338         do {
339
340                 /* This loop waits for keyboard input.  If the keepalive
341                  * timer expires, it sends a keepalive to the server if
342                  * necessary and then waits again.
343                  */
344                 do {
345                         do_keepalive();
346
347                         FD_ZERO(&rfds);
348                         FD_SET(0, &rfds);
349                         tv.tv_sec = S_KEEPALIVE;
350                         tv.tv_usec = 0;
351
352                         time(&now);
353                         if (((now - start_time) > SLEEPING)
354                             && (SLEEPING != 0) && (getppid() == 1)) {
355                                 printf("Sleeping? Call again.\n");
356                                 logoff(SIGALRM);
357                         }
358                         select(1, &rfds, NULL, NULL, &tv);
359                 } while (!FD_ISSET(0, &rfds));
360
361
362
363
364                 /* At this point, there's input, so fetch it.
365                  * (There's a hole in the bucket...)
366                  */
367                 read(0, inbuf, 1);
368                 a = inbuf[0];
369                 if (a == 127)
370                         a = 8;
371                 if (a > 126)
372                         a = 0;
373                 if (a == 10)
374                         a = 13;
375                 if (((a != 4) && (a != 13) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
376                     && ((a < 32) || (a > 126)))
377                         a = 0;
378         } while (a == 0);
379         return (a);
380 }
381
382
383 int yesno(void)
384 {                               /* Returns 1 for yes, 0 for no */
385         int a;
386         while (1) {
387                 a = inkey();
388                 a = tolower(a);
389                 if (a == 'y') {
390                         printf("Yes\n");
391                         return (1);
392                 }
393                 if (a == 'n') {
394                         printf("No\n");
395                         return (0);
396                 }
397         }
398 }
399
400 /* Returns 1 for yes, 0 for no, arg is default value */
401 int yesno_d(int d)
402 {
403         int a;
404         while (1) {
405                 a = inkey();
406                 a = tolower(a);
407                 if (a == 13)
408                         a = (d ? 'y' : 'n');
409                 if (a == 'y') {
410                         printf("Yes\n");
411                         return (1);
412                 }
413                 if (a == 'n') {
414                         printf("No\n");
415                         return (0);
416                 }
417         }
418 }
419
420
421
422
423 /* Gets a line from the terminal */
424 /* string == Pointer to string buffer */
425 /* lim == Maximum length - if negative, no-show */
426 void getline(char *string, int lim) 
427 {
428         int a, b;
429         char flag = 0;
430
431         if (lim < 0) {
432                 lim = (0 - lim);
433                 flag = 1;
434         }
435         strcpy(string, "");
436         gl_string = string;
437         async_ka_start();
438       GLA:a = inkey();
439         a = (a & 127);
440         if ((a == 8) && (strlen(string) == 0))
441                 goto GLA;
442         if ((a != 13) && (a != 8) && (strlen(string) == lim))
443                 goto GLA;
444         if ((a == 8) && (string[0] != 0)) {
445                 string[strlen(string) - 1] = 0;
446                 putc(8, stdout);
447                 putc(32, stdout);
448                 putc(8, stdout);
449                 goto GLA;
450         }
451         if ((a == 13) || (a == 10)) {
452                 putc(13, stdout);
453                 putc(10, stdout);
454                 async_ka_end();
455                 return;
456         }
457         if (a < 32)
458                 a = '.';
459         b = strlen(string);
460         string[b] = a;
461         string[b + 1] = 0;
462         if (flag == 0)
463                 putc(a, stdout);
464         if (flag == 1)
465                 putc('*', stdout);
466         goto GLA;
467 }
468
469
470 /*
471  * strprompt()  -  prompt for a string, print the existing value and
472  *                 allow the user to press return to keep it...
473  */
474 void strprompt(char *prompt, char *str, int len)
475 {
476         char buf[128];
477         print_express();
478         color(DIM_WHITE);
479         printf("%s ", prompt);
480         color(DIM_MAGENTA);
481         printf("[");
482         color(BRIGHT_MAGENTA);
483         printf("%s", str);
484         color(DIM_MAGENTA);
485         printf("]");
486         color(DIM_WHITE);
487         printf(": ");
488         color(BRIGHT_CYAN);
489         getline(buf, len);
490         if (buf[0] != 0)
491                 strcpy(str, buf);
492         color(DIM_WHITE);
493 }
494
495 /*
496  * boolprompt()  -  prompt for a yes/no, print the existing value and
497  *                  allow the user to press return to keep it...
498  */
499 int boolprompt(char *prompt, int prev_val)
500 {
501         int r;
502
503         color(DIM_WHITE);
504         printf("%s ", prompt);
505         color(DIM_MAGENTA);
506         printf(" [");
507         color(BRIGHT_MAGENTA);
508         printf("%s", (prev_val ? "Yes" : "No"));
509         color(DIM_MAGENTA);
510         printf("]: ");
511         color(BRIGHT_CYAN);
512         r = (yesno_d(prev_val));
513         color(DIM_WHITE);
514         return r;
515 }
516
517 /* 
518  * intprompt()  -  like strprompt(), except for an integer
519  *                 (note that it RETURNS the new value!)
520  */
521 int intprompt(char *prompt, int ival, int imin, int imax)
522 {
523         char buf[16];
524         int i;
525         int p;
526
527         do {
528                 i = ival;
529                 snprintf(buf, sizeof buf, "%d", i);
530                 strprompt(prompt, buf, 15);
531                 i = atoi(buf);
532                 for (p=0; p<strlen(buf); ++p) {
533                         if ( (!isdigit(buf[p]))
534                            && ( (buf[p]!='-') || (p!=0) )  )
535                                 i = imin - 1;
536                 }
537                 if (i < imin)
538                         printf("*** Must be no less than %d.\n", imin);
539                 if (i > imax)
540                         printf("*** Must be no more than %d.\n", imax);
541         } while ((i < imin) || (i > imax));
542         return (i);
543 }
544
545 /* 
546  * newprompt()  -  prompt for a string with no existing value
547  *                 (clears out string buffer first)
548  */
549 void newprompt(char *prompt, char *str, int len)
550 {
551         color(BRIGHT_MAGENTA);
552         printf("%s", prompt);
553         color(DIM_MAGENTA);
554         getline(str, len);
555         color(DIM_WHITE);
556 }
557
558
559 int lkey(void)
560 {                               /* returns a lower case value */
561         int a;
562         a = inkey();
563         if (isupper(a))
564                 a = tolower(a);
565         return (a);
566 }
567
568 /*
569  * parse the citadel.rc file
570  */
571 void load_command_set(void)
572 {
573         FILE *ccfile;
574         char buf[256];
575         struct citcmd *cptr;
576         struct citcmd *lastcmd = NULL;
577         int a, d;
578         int b = 0;
579
580
581         /* first, set up some defaults for non-required variables */
582
583         strcpy(editor_path, "");
584         strcpy(printcmd, "");
585         strcpy(rc_username, "");
586         strcpy(rc_password, "");
587         rc_floor_mode = 0;
588         rc_exp_beep = 1;
589         rc_allow_attachments = 0;
590         rc_remember_passwords = 0;
591         strcpy(rc_exp_cmd, "");
592         rc_display_message_numbers = 0;
593         rc_force_mail_prompts = 0;
594         rc_ansi_color = 0;
595         strcpy(rc_url_cmd, "");
596
597         /* now try to open the citadel.rc file */
598
599         ccfile = NULL;
600         if (getenv("HOME") != NULL) {
601                 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
602                 ccfile = fopen(buf, "r");
603         }
604         if (ccfile == NULL) {
605                 ccfile = fopen("/usr/local/lib/citadel.rc", "r");
606         }
607         if (ccfile == NULL) {
608                 snprintf(buf, sizeof buf, "%s/citadel.rc", BBSDIR);
609                 ccfile = fopen(buf, "r");
610         }
611         if (ccfile == NULL) {
612                 ccfile = fopen("./citadel.rc", "r");
613         }
614         if (ccfile == NULL) {
615                 perror("commands: cannot open citadel.rc");
616                 logoff(errno);
617         }
618         while (fgets(buf, 256, ccfile) != NULL) {
619                 while ((strlen(buf) > 0) ? (isspace(buf[strlen(buf) - 1])) : 0)
620                         buf[strlen(buf) - 1] = 0;
621
622                 if (!strncasecmp(buf, "editor=", 7))
623                         strcpy(editor_path, &buf[7]);
624
625                 if (!strncasecmp(buf, "printcmd=", 9))
626                         strcpy(printcmd, &buf[9]);
627
628                 if (!strncasecmp(buf, "expcmd=", 7))
629                         strcpy(rc_exp_cmd, &buf[7]);
630
631                 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
632                         have_xterm = (char) atoi(&buf[24]);
633
634                 if (!strncasecmp(buf, "use_floors=", 11)) {
635                         if (!strcasecmp(&buf[11], "yes"))
636                                 rc_floor_mode = RC_YES;
637                         if (!strcasecmp(&buf[11], "no"))
638                                 rc_floor_mode = RC_NO;
639                         if (!strcasecmp(&buf[11], "default"))
640                                 rc_floor_mode = RC_DEFAULT;
641                 }
642                 if (!strncasecmp(buf, "beep=", 5)) {
643                         rc_exp_beep = atoi(&buf[5]);
644                 }
645                 if (!strncasecmp(buf, "allow_attachments=", 18)) {
646                         rc_allow_attachments = atoi(&buf[18]);
647                 }
648                 if (!strncasecmp(buf, "remember_passwords=", 19)) {
649                         rc_remember_passwords = atoi(&buf[19]);
650                 }
651                 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
652                         rc_display_message_numbers = atoi(&buf[24]);
653                 }
654                 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
655                         rc_force_mail_prompts = atoi(&buf[19]);
656                 }
657                 if (!strncasecmp(buf, "ansi_color=", 11)) {
658                         if (!strncasecmp(&buf[11], "on", 2))
659                                 rc_ansi_color = 1;
660                         if (!strncasecmp(&buf[11], "auto", 4))
661                                 rc_ansi_color = 2;      /* autodetect */
662                         if (!strncasecmp(&buf[11], "user", 4))
663                                 rc_ansi_color = 3;      /* user config */
664                 }
665                 if (!strncasecmp(buf, "username=", 9))
666                         strcpy(rc_username, &buf[9]);
667
668                 if (!strncasecmp(buf, "password=", 9))
669                         strcpy(rc_password, &buf[9]);
670
671                 if (!strncasecmp(buf, "urlcmd=", 7))
672                         strcpy(rc_url_cmd, &buf[7]);
673
674                 if (!strncasecmp(buf, "cmd=", 4)) {
675                         strcpy(buf, &buf[4]);
676
677                         cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
678
679                         cptr->c_cmdnum = atoi(buf);
680                         for (d = strlen(buf); d >= 0; --d)
681                                 if (buf[d] == ',')
682                                         b = d;
683                         strcpy(buf, &buf[b + 1]);
684
685                         cptr->c_axlevel = atoi(buf);
686                         for (d = strlen(buf); d >= 0; --d)
687                                 if (buf[d] == ',')
688                                         b = d;
689                         strcpy(buf, &buf[b + 1]);
690
691                         for (a = 0; a < 5; ++a)
692                                 cptr->c_keys[a][0] = 0;
693
694                         a = 0;
695                         b = 0;
696                         buf[strlen(buf) + 1] = 0;
697                         while (strlen(buf) > 0) {
698                                 b = strlen(buf);
699                                 for (d = strlen(buf); d >= 0; --d)
700                                         if (buf[d] == ',')
701                                                 b = d;
702                                 strncpy(cptr->c_keys[a], buf, b);
703                                 cptr->c_keys[a][b] = 0;
704                                 if (buf[b] == ',')
705                                         strcpy(buf, &buf[b + 1]);
706                                 else
707                                         strcpy(buf, "");
708                                 ++a;
709                         }
710
711                         cptr->next = NULL;
712                         if (cmdlist == NULL)
713                                 cmdlist = cptr;
714                         else
715                                 lastcmd->next = cptr;
716                         lastcmd = cptr;
717                 }
718         }
719         fclose(ccfile);
720 }
721
722
723
724 /*
725  * return the key associated with a command
726  */
727 char keycmd(char *cmdstr)
728 {
729         int a;
730
731         for (a = 0; a < strlen(cmdstr); ++a)
732                 if (cmdstr[a] == '&')
733                         return (tolower(cmdstr[a + 1]));
734         return (0);
735 }
736
737
738 /*
739  * Output the string from a key command without the ampersand
740  * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
741  */
742 char *cmd_expand(char *strbuf, int mode)
743 {
744         int a;
745         static char exp[64];
746         char buf[256];
747
748         strcpy(exp, strbuf);
749
750         for (a = 0; a < strlen(exp); ++a) {
751                 if (strbuf[a] == '&') {
752
753                         if (mode == 0) {
754                                 strcpy(&exp[a], &exp[a + 1]);
755                         }
756                         if (mode == 1) {
757                                 exp[a] = '<';
758                                 strcpy(buf, &exp[a + 2]);
759                                 exp[a + 2] = '>';
760                                 exp[a + 3] = 0;
761                                 strcat(exp, buf);
762                         }
763                 }
764                 if (!strncmp(&exp[a], "^r", 2)) {
765                         strcpy(buf, exp);
766                         strcpy(&exp[a], room_name);
767                         strcat(exp, &buf[a + 2]);
768                 }
769                 if (!strncmp(&exp[a], "^c", 2)) {
770                         exp[a] = ',';
771                         strcpy(&exp[a + 1], &exp[a + 2]);
772                 }
773         }
774
775         return (exp);
776 }
777
778
779
780 /*
781  * Comparison function to determine if entered commands match a
782  * command loaded from the config file.
783  */
784 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
785 {
786         int a;
787         int cmdax;
788
789         cmdax = 0;
790         if (is_room_aide)
791                 cmdax = 1;
792         if (axlevel >= 6)
793                 cmdax = 2;
794
795         for (a = 0; a < ncomp; ++a) {
796                 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
797                     || (cptr->c_axlevel > cmdax))
798                         return (0);
799         }
800         return (1);
801 }
802
803
804 /*
805  * This function returns 1 if a given command requires a string input
806  */
807 int requires_string(struct citcmd *cptr, int ncomp)
808 {
809         int a;
810         char buf[64];
811
812         strcpy(buf, cptr->c_keys[ncomp - 1]);
813         for (a = 0; a < strlen(buf); ++a) {
814                 if (buf[a] == ':')
815                         return (1);
816         }
817         return (0);
818 }
819
820
821 /*
822  * Input a command at the main prompt.
823  * This function returns an integer command number.  If the command prompts
824  * for a string then it is placed in the supplied buffer.
825  */
826 int getcmd(char *argbuf)
827 {
828         char cmdbuf[5];
829         int cmdspaces[5];
830         int cmdpos;
831         int ch;
832         int a;
833         int got;
834         int this_lazy_cmd;
835         struct citcmd *cptr;
836
837         /*
838          * Starting a new command now, so set sigcaught to 0.  This variable
839          * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
840          * been interrupted by a keypress.
841          */
842         sigcaught = 0;
843
844         /* Switch color support on or off if we're in user mode */
845         if (rc_ansi_color == 3) {
846                 if (userflags & US_COLOR)
847                         enable_color = 1;
848                 else
849                         enable_color = 0;
850         }
851         /* if we're running in idiot mode, display a cute little menu */
852         IFNEXPERT formout("mainmenu");
853
854         print_express();        /* print express messages if there are any */
855         strcpy(argbuf, "");
856         cmdpos = 0;
857         for (a = 0; a < 5; ++a)
858                 cmdbuf[a] = 0;
859         /* now the room prompt... */
860         ok_to_interrupt = 1;
861         color(BRIGHT_WHITE);
862         printf("\n%s", room_name);
863         color(DIM_WHITE);
864         printf("%c ", room_prompt(room_flags));
865         fflush(stdout);
866
867         while (1) {
868                 ch = inkey();
869                 ok_to_interrupt = 0;
870
871                 /* Handle the backspace key, but only if there's something
872                  * to backspace over...
873                  */
874                 if ((ch == 8) && (cmdpos > 0)) {
875                         back(cmdspaces[cmdpos - 1] + 1);
876                         cmdbuf[cmdpos] = 0;
877                         --cmdpos;
878                 }
879                 /* Spacebar invokes "lazy traversal" commands */
880                 if ((ch == 32) && (cmdpos == 0)) {
881                         this_lazy_cmd = next_lazy_cmd;
882                         if (this_lazy_cmd == 13)
883                                 next_lazy_cmd = 5;
884                         if (this_lazy_cmd == 5)
885                                 next_lazy_cmd = 13;
886                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
887                                 if (cptr->c_cmdnum == this_lazy_cmd) {
888                                         for (a = 0; a < 5; ++a)
889                                                 if (cptr->c_keys[a][0] != 0)
890                                                         printf("%s ", cmd_expand(
891                                                                                         cptr->c_keys[a], 0));
892                                         printf("\n");
893                                         return (this_lazy_cmd);
894                                 }
895                         }
896                         printf("\n");
897                         return (this_lazy_cmd);
898                 }
899                 /* Otherwise, process the command */
900                 cmdbuf[cmdpos] = tolower(ch);
901
902                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
903                         if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
904
905                                 printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
906                                 cmdspaces[cmdpos] = strlen(
907                                     cmd_expand(cptr->c_keys[cmdpos], 0));
908                                 if (cmdpos < 4)
909                                         if ((cptr->c_keys[cmdpos + 1]) != 0)
910                                                 putc(' ', stdout);
911                                 ++cmdpos;
912                         }
913                 }
914
915                 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
916                         if (cmdmatch(cmdbuf, cptr, 5)) {
917                                 /* We've found our command. */
918                                 if (requires_string(cptr, cmdpos)) {
919                                         getline(argbuf, 32);
920                                 } else {
921                                         printf("\n");
922                                 }
923
924                                 /* If this command is one that changes rooms,
925                                  * then the next lazy-command (space bar)
926                                  * should be "read new" instead of "goto"
927                                  */
928                                 if ((cptr->c_cmdnum == 5)
929                                     || (cptr->c_cmdnum == 6)
930                                     || (cptr->c_cmdnum == 47)
931                                     || (cptr->c_cmdnum == 52)
932                                     || (cptr->c_cmdnum == 16)
933                                     || (cptr->c_cmdnum == 20))
934                                         next_lazy_cmd = 13;
935
936                                 return (cptr->c_cmdnum);
937
938                         }
939                 }
940
941                 if (ch == '?') {
942                         printf("\rOne of ...                         \n");
943                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
944                                 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
945                                         for (a = 0; a < 5; ++a) {
946                                                 printf("%s ", cmd_expand(cptr->c_keys[a], 1));
947                                         }
948                                         printf("\n");
949                                 }
950                         }
951
952                         printf("\n%s%c ", room_name, room_prompt(room_flags));
953                         got = 0;
954                         for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
955                                 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
956                                         for (a = 0; a < cmdpos; ++a) {
957                                                 printf("%s ",
958                                                        cmd_expand(cptr->c_keys[a], 0));
959                                         }
960                                         got = 1;
961                                 }
962                         }
963                 }
964         }
965
966 }
967
968
969
970
971
972 /*
973  * set tty modes.  commands are:
974  * 
975  * 01- set to bbs mode
976  * 2 - save current settings for later restoral
977  * 3 - restore saved settings
978  */
979 #ifdef HAVE_TERMIOS_H
980 void sttybbs(int cmd)
981 {                               /* SysV version of sttybbs() */
982         struct termios live;
983         static struct termios saved_settings;
984         static int last_cmd = 0;
985
986         if (cmd == SB_LAST)
987                 cmd = last_cmd;
988         else
989                 last_cmd = cmd;
990
991         if ((cmd == 0) || (cmd == 1)) {
992                 tcgetattr(0, &live);
993                 live.c_iflag = ISTRIP | IXON | IXANY;
994                 live.c_oflag = OPOST | ONLCR;
995                 live.c_lflag = ISIG | NOFLSH;
996
997                 live.c_cc[VINTR] = (-1);
998                 live.c_cc[VQUIT] = (-1);
999
1000                 /* do we even need this stuff anymore? */
1001                 /* live.c_line=0; */
1002                 live.c_cc[VERASE] = 8;
1003                 live.c_cc[VKILL] = 24;
1004                 live.c_cc[VEOF] = 1;
1005                 live.c_cc[VEOL] = 255;
1006                 live.c_cc[VEOL2] = 0;
1007                 live.c_cc[VSTART] = 0;
1008                 tcsetattr(0, TCSADRAIN, &live);
1009         }
1010         if (cmd == 2) {
1011                 tcgetattr(0, &saved_settings);
1012         }
1013         if (cmd == 3) {
1014                 tcsetattr(0, TCSADRAIN, &saved_settings);
1015         }
1016 }
1017 #else
1018 void sttybbs(int cmd)
1019 {                               /* BSD version of sttybbs() */
1020         struct sgttyb live;
1021         static struct sgttyb saved_settings;
1022
1023         if ((cmd == 0) || (cmd == 1)) {
1024                 gtty(0, &live);
1025                 live.sg_flags |= CBREAK;
1026                 live.sg_flags |= CRMOD;
1027                 live.sg_flags |= NL1;
1028                 live.sg_flags &= ~ECHO;
1029                 if (cmd == 1)
1030                         live.sg_flags |= NOFLSH;
1031                 stty(0, &live);
1032         }
1033         if (cmd == 2) {
1034                 gtty(0, &saved_settings);
1035         }
1036         if (cmd == 3) {
1037                 stty(0, &saved_settings);
1038         }
1039 }
1040 #endif
1041
1042
1043 /*
1044  * display_help()  -  help file viewer
1045  */
1046 void display_help(char *name)
1047 {
1048         formout(name);
1049 }
1050
1051
1052 /*
1053  * fmout()  -  Citadel text formatter and paginator
1054  */
1055 int fmout(
1056         int width,      /* screen width to use */
1057         FILE *fp,       /* file to read from, or NULL to read from server */
1058         char pagin,     /* nonzero if we should use the paginator */
1059         int height,     /* screen height to use */
1060         int starting_lp,/* starting value for lines_printed, -1 for global */
1061         char subst)     /* nonzero if we should use hypertext mode */
1062 {
1063         int a, b, c, d, old;
1064         int real = (-1);
1065         char aaa[140];
1066         char buffer[512];
1067         int eof_flag = 0;
1068
1069         num_urls = 0;   /* Start with a clean slate of embedded URL's */
1070
1071         if (starting_lp >= 0) {
1072                 lines_printed = starting_lp;
1073         }
1074         strcpy(aaa, "");
1075         old = 255;
1076         strcpy(buffer, "");
1077         c = 1;                  /* c is the current pos */
1078
1079 FMTA:   while ((eof_flag == 0) && (strlen(buffer) < 126)) {
1080                 if (fp != NULL) {       /* read from file */
1081                         if (feof(fp))
1082                                 eof_flag = 1;
1083                         if (eof_flag == 0) {
1084                                 a = getc(fp);
1085                                 buffer[strlen(buffer) + 1] = 0;
1086                                 buffer[strlen(buffer)] = a;
1087                         }
1088                 } else {        /* read from server */
1089                         d = strlen(buffer);
1090                         serv_gets(&buffer[d]);
1091                         while ((!isspace(buffer[d])) && (isspace(buffer[strlen(buffer) - 1])))
1092                                 buffer[strlen(buffer) - 1] = 0;
1093                         if (!strcmp(&buffer[d], "000")) {
1094                                 buffer[d] = 0;
1095                                 eof_flag = 1;
1096                                 while (isspace(buffer[strlen(buffer) - 1]))
1097                                         buffer[strlen(buffer) - 1] = 0;
1098                         }
1099                         d = strlen(buffer);
1100                         buffer[d] = 10;
1101                         buffer[d + 1] = 0;
1102                 }
1103         }
1104
1105         if ( (!strncasecmp(buffer, "http://", 7))
1106            || (!strncasecmp(buffer, "ftp://", 6)) ) {
1107                 safestrncpy(urls[num_urls], buffer, 255);
1108                 for (a=0; a<strlen(urls[num_urls]); ++a) {
1109                         b = urls[num_urls][a];
1110                         if ( (b==' ') || (b==')') || (b=='>') || (b==10)
1111                            || (b==13) || (b==9) || (b=='\"') )
1112                                 urls[num_urls][a] = 0;
1113                 }
1114                 ++num_urls;
1115         }
1116
1117         buffer[strlen(buffer) + 1] = 0;
1118         a = buffer[0];
1119         strcpy(buffer, &buffer[1]);
1120
1121         old = real;
1122         real = a;
1123         if (a <= 0)
1124                 goto FMTEND;
1125
1126         if (((a == 13) || (a == 10)) && (old != 13) && (old != 10))
1127                 a = 32;
1128         if (((old == 13) || (old == 10)) && (isspace(real))) {
1129                 printf("\n");
1130                 ++lines_printed;
1131                 lines_printed = checkpagin(lines_printed, pagin, height);
1132                 c = 1;
1133         }
1134         if (a > 126)
1135                 goto FMTA;
1136
1137         if (a > 32) {
1138                 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
1139                         printf("\n%s", aaa);
1140                         c = strlen(aaa);
1141                         aaa[0] = 0;
1142                         ++lines_printed;
1143                         lines_printed = checkpagin(lines_printed, pagin, height);
1144                 }
1145                 b = strlen(aaa);
1146                 aaa[b] = a;
1147                 aaa[b + 1] = 0;
1148         }
1149         if (a == 32) {
1150                 if ((strlen(aaa) + c) > (width - 5)) {
1151                         c = 1;
1152                         printf("\n");
1153                         ++lines_printed;
1154                         lines_printed = checkpagin(lines_printed, pagin, height);
1155                 }
1156                 printf("%s ", aaa);
1157                 ++c;
1158                 c = c + strlen(aaa);
1159                 strcpy(aaa, "");
1160                 goto FMTA;
1161         }
1162         if ((a == 13) || (a == 10)) {
1163                 printf("%s\n", aaa);
1164                 c = 1;
1165                 ++lines_printed;
1166                 lines_printed = checkpagin(lines_printed, pagin, height);
1167                 if (sigcaught) goto OOPS;
1168                 strcpy(aaa, "");
1169                 goto FMTA;
1170         }
1171         goto FMTA;
1172
1173         /* keypress caught; drain the server */
1174 OOPS:   do {
1175                 serv_gets(aaa);
1176         } while (strcmp(aaa, "000"));
1177
1178 FMTEND: printf("\n");
1179         ++lines_printed;
1180         lines_printed = checkpagin(lines_printed, pagin, height);
1181         return (sigcaught);
1182 }
1183
1184
1185 /*
1186  * support ANSI color if defined
1187  */
1188 void color(int colornum)
1189 {
1190         static int is_bold = 0;
1191         static int hold_color, current_color;
1192
1193         if (colornum == COLOR_PUSH) {
1194                 hold_color = current_color;
1195                 return;
1196         }
1197
1198         if (colornum == COLOR_POP) {
1199                 color(hold_color);
1200                 return;
1201         }
1202
1203         current_color = colornum;
1204         if (enable_color) {
1205                 /* When switching to dim white, actually output an 'original
1206                  * pair' sequence -- this looks better on black-on-white
1207                  * terminals.
1208                  */
1209                 if (colornum == DIM_WHITE)
1210                         printf("\033[39;49m");
1211                 else
1212                         printf("\033[3%d;40m", (colornum & 7));
1213
1214                 if ((colornum >= 8) && (is_bold == 0)) {
1215                         printf("\033[1m");
1216                         is_bold = 1;
1217                 } else if ((colornum < 8) && (is_bold == 1)) {
1218                         printf("\033[0m");
1219                         is_bold = 0;
1220                 }
1221                 fflush(stdout);
1222         }
1223 }
1224
1225 void cls(int colornum)
1226 {
1227         if (enable_color) {
1228                 printf("\033[4%dm\033[2J\033[H\033[0m", colornum);
1229                 fflush(stdout);
1230         }
1231 }
1232
1233
1234 /*
1235  * Detect whether ANSI color is available (answerback)
1236  */
1237 void send_ansi_detect(void)
1238 {
1239         if (rc_ansi_color == 2) {
1240                 printf("\033[c");
1241                 fflush(stdout);
1242                 time(&AnsiDetect);
1243         }
1244 }
1245
1246 void look_for_ansi(void)
1247 {
1248         fd_set rfds;
1249         struct timeval tv;
1250         char abuf[512];
1251         time_t now;
1252         int a;
1253
1254         if (rc_ansi_color == 0) {
1255                 enable_color = 0;
1256         } else if (rc_ansi_color == 1) {
1257                 enable_color = 1;
1258         } else if (rc_ansi_color == 2) {
1259
1260                 /* otherwise, do the auto-detect */
1261
1262                 strcpy(abuf, "");
1263
1264                 time(&now);
1265                 if ((now - AnsiDetect) < 2)
1266                         sleep(1);
1267
1268                 do {
1269                         FD_ZERO(&rfds);
1270                         FD_SET(0, &rfds);
1271                         tv.tv_sec = 0;
1272                         tv.tv_usec = 1;
1273
1274                         select(1, &rfds, NULL, NULL, &tv);
1275                         if (FD_ISSET(0, &rfds)) {
1276                                 abuf[strlen(abuf) + 1] = 0;
1277                                 read(0, &abuf[strlen(abuf)], 1);
1278                         }
1279                 } while (FD_ISSET(0, &rfds));
1280
1281                 for (a = 0; a < strlen(abuf); ++a) {
1282                         if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1283                             && (abuf[a + 2] == '?')) {
1284                                 enable_color = 1;
1285                         }
1286                 }
1287         }
1288 }
1289
1290
1291 /*
1292  * Display key options (highlight hotkeys inside angle brackets)
1293  */
1294 void keyopt(char *buf) {
1295         int i;
1296
1297         color(DIM_WHITE);
1298         for (i=0; i<strlen(buf); ++i) {
1299                 if (buf[i]=='<') {
1300                         putc(buf[i], stdout);
1301                         color(BRIGHT_MAGENTA);
1302                 } else {
1303                         if (buf[i]=='>') {
1304                                 color(DIM_WHITE);
1305                         }
1306                         putc(buf[i], stdout);
1307                 }
1308         }
1309         color(DIM_WHITE);
1310 }
1311
1312
1313
1314 /*
1315  * Present a key-menu line choice type of thing
1316  */
1317 char keymenu(char *menuprompt, char *menustring) {
1318         int i, c, a;
1319         int choices;
1320         int do_prompt = 0;
1321         char buf[256];
1322         int ch;
1323         int display_prompt = 1;
1324
1325         choices = num_tokens(menustring, '|');
1326
1327         if (menuprompt != NULL) do_prompt = 1;
1328         if (menuprompt != NULL) if (strlen(menuprompt)==0) do_prompt = 0;
1329
1330         while (1) {
1331                 if (display_prompt) {
1332                         if (do_prompt) {
1333                                 printf("%s ", menuprompt);
1334                         } 
1335                         else {
1336                                 for (i=0; i<choices; ++i) {
1337                                         extract(buf, menustring, i);
1338                                         keyopt(buf);
1339                                         printf(" ");
1340                                 }
1341                         }
1342                         printf(" -> ");
1343                         display_prompt = 0;
1344                 }
1345                 ch = lkey();
1346         
1347                 if ( (do_prompt) && (ch=='?') ) {
1348                         printf("\rOne of...                               ");
1349                         printf("                                      \n");
1350                         for (i=0; i<choices; ++i) {
1351                                 extract(buf, menustring, i);
1352                                 printf("   ");
1353                                 keyopt(buf);
1354                                 printf("\n");
1355                         }
1356                         printf("\n");
1357                         display_prompt = 1;
1358                 }
1359
1360                 for (i=0; i<choices; ++i) {
1361                         extract(buf, menustring, i);
1362                         for (c=1; c<strlen(buf); ++c) {
1363                                 if ( (ch == tolower(buf[c]))
1364                                    && (buf[c-1]=='<')
1365                                    && (buf[c+1]=='>') ) {
1366                                         for (a=0; a<strlen(buf); ++a) {
1367                                                 if ( (a!=(c-1)) && (a!=(c+1))) {
1368                                                         putc(buf[a], stdout);
1369                                                 }
1370                                         }
1371                                         printf("\n\n");
1372                                         return ch;
1373                                 }
1374                         }
1375                 }
1376         }
1377 }