* Removed some temporary diagnostics that had been left in serv_sieve.c
[citadel.git] / citadel / commands.c
blob4b71f698e2fcba024445719baf145b82f0df6d3c
1 /*
2 * $Id$
4 * This file contains functions which implement parts of the
5 * text-mode user interface.
7 */
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>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
20 # include <time.h>
21 #else
22 # if HAVE_SYS_TIME_H
23 # include <sys/time.h>
24 # else
25 # include <time.h>
26 # endif
27 #endif
29 #ifdef HAVE_TERMIOS_H
30 #include <termios.h>
31 #else
32 #include <sgtty.h>
33 #endif
35 #ifdef HAVE_SYS_SELECT_H
36 #include <sys/select.h>
37 #endif
39 #ifdef THREADED_CLIENT
40 #include <pthread.h>
41 #endif
43 #include <signal.h>
44 #include <errno.h>
45 #include <stdarg.h>
46 #include <libcitadel.h>
47 #include "citadel.h"
48 #include "citadel_ipc.h"
49 #include "commands.h"
50 #include "messages.h"
51 #include "citadel_decls.h"
52 #include "routines.h"
53 #include "routines2.h"
54 #include "rooms.h"
55 #include "client_chat.h"
56 #include "citadel_dirs.h"
57 #ifndef HAVE_SNPRINTF
58 #include "snprintf.h"
59 #endif
60 #include "screen.h"
61 #include "ecrash.h"
63 struct citcmd {
64 struct citcmd *next;
65 int c_cmdnum;
66 int c_axlevel;
67 char c_keys[5][64];
70 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
73 int rc_exp_beep;
74 char rc_exp_cmd[1024];
75 int rc_allow_attachments;
76 int rc_display_message_numbers;
77 int rc_force_mail_prompts;
78 int rc_remember_passwords;
79 int rc_ansi_color;
80 int rc_color_use_bg;
81 int rc_prompt_control = 0;
82 time_t rc_idle_threshold = (time_t)900;
83 char rc_url_cmd[SIZ];
84 char rc_open_cmd[SIZ];
85 char rc_gotmail_cmd[SIZ];
87 char *gl_string;
88 int next_lazy_cmd = 5;
90 int lines_printed = 0; /* line count for paginator */
91 extern int screenwidth, screenheight;
92 extern int termn8;
93 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
95 struct citcmd *cmdlist = NULL;
98 /* these variables are local to this module */
99 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
100 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
101 time_t AnsiDetect; /* when did we send the detect code? */
102 int enable_color = 0; /* nonzero for ANSI color */
108 * If an interesting key has been pressed, return its value, otherwise 0
110 char was_a_key_pressed(void) {
111 fd_set rfds;
112 struct timeval tv;
113 int the_character;
114 int retval;
116 FD_ZERO(&rfds);
117 FD_SET(0, &rfds);
118 tv.tv_sec = 0;
119 tv.tv_usec = 0;
120 retval = select(1, &rfds, NULL, NULL, &tv);
122 /* Careful! Disable keepalives during keyboard polling; we're probably
123 * in the middle of a data transfer from the server, in which case
124 * sending a NOOP would throw the client protocol out of sync.
126 if (FD_ISSET(0, &rfds)) {
127 set_keepalives(KA_NO);
128 the_character = inkey();
129 set_keepalives(KA_YES);
131 else {
132 the_character = 0;
134 return(the_character);
142 * Check to see if we need to pause at the end of a screen.
143 * If we do, we have to switch to half keepalives during the pause because
144 * we are probably in the middle of a server operation and the NOOP command
145 * would confuse everything.
147 int checkpagin(int lp, unsigned int pagin, unsigned int height)
149 int thekey;
151 if (sigcaught) return(lp);
152 thekey = was_a_key_pressed();
153 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
154 thekey = STOP_KEY;
155 if (thekey == 'n' || thekey == 'N')
156 thekey = NEXT_KEY;
157 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
158 if (sigcaught) return(lp);
160 if (!pagin) return(0);
161 if (lp>=(height-1)) {
162 set_keepalives(KA_HALF);
163 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
164 set_keepalives(KA_YES);
165 return(0);
167 return(lp);
174 * pprintf() ... paginated version of printf()
176 void pprintf(const char *format, ...) {
177 va_list arg_ptr;
178 static char buf[4096]; /* static for performance, change if needed */
179 int i;
181 /* If sigcaught is nonzero, a keypress has interrupted this and we
182 * should just drain output.
184 if (sigcaught) return;
186 /* Otherwise, start spewing... */
187 va_start(arg_ptr, format);
188 vsnprintf(buf, sizeof(buf), format, arg_ptr);
189 va_end(arg_ptr);
191 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
192 scr_putc(buf[i]);
193 if (buf[i]==10) {
194 ++lines_printed;
195 lines_printed = checkpagin(lines_printed,
196 (userflags & US_PAGINATOR),
197 screenheight);
205 * print_instant() - print instant messages if there are any
207 void print_instant(void)
209 char buf[1024];
210 FILE *outpipe;
211 time_t timestamp;
212 struct tm stamp;
213 int flags = 0;
214 char sender[64];
215 char node[64];
216 char *listing = NULL;
217 int r; /* IPC result code */
219 if (instant_msgs == 0)
220 return;
222 if (rc_exp_beep) {
223 ctdl_beep();
225 if (IsEmptyStr(rc_exp_cmd)) {
226 color(BRIGHT_RED);
227 scr_printf("\r---");
230 while (instant_msgs != 0) {
231 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
232 if (r / 100 != 1)
233 return;
235 instant_msgs = extract_int(buf, 0);
236 timestamp = extract_long(buf, 1);
237 flags = extract_int(buf, 2);
238 extract_token(sender, buf, 3, '|', sizeof sender);
239 extract_token(node, buf, 4, '|', sizeof node);
240 strcpy(last_paged, sender);
242 localtime_r(&timestamp, &stamp);
244 /* If the page is a Logoff Request, honor it. */
245 if (flags & 2) {
246 termn8 = 1;
247 return;
250 if (!IsEmptyStr(rc_exp_cmd)) {
251 outpipe = popen(rc_exp_cmd, "w");
252 if (outpipe != NULL) {
253 /* Header derived from flags */
254 if (flags & 2)
255 fprintf(outpipe,
256 "Please log off now, as requested ");
257 else if (flags & 1)
258 fprintf(outpipe, "Broadcast message ");
259 else if (flags & 4)
260 fprintf(outpipe, "Chat request ");
261 else
262 fprintf(outpipe, "Message ");
263 /* Timestamp. Can this be improved? */
264 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
265 fprintf(outpipe, "at 12:%02d%cm",
266 stamp.tm_min,
267 stamp.tm_hour ? 'p' : 'a');
268 else if (stamp.tm_hour > 12) /* pm */
269 fprintf(outpipe, "at %d:%02dpm",
270 stamp.tm_hour - 12,
271 stamp.tm_min);
272 else /* am */
273 fprintf(outpipe, "at %d:%02dam",
274 stamp.tm_hour, stamp.tm_min);
275 fprintf(outpipe, " from %s", sender);
276 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
277 fprintf(outpipe, " @%s", node);
278 fprintf(outpipe, ":\n%s\n", listing);
279 pclose(outpipe);
280 if (instant_msgs == 0)
281 return;
282 continue;
285 /* fall back to built-in instant message display */
286 scr_printf("\n");
287 lines_printed++;
289 /* Header derived from flags */
290 if (flags & 2)
291 scr_printf("Please log off now, as requested ");
292 else if (flags & 1)
293 scr_printf("Broadcast message ");
294 else if (flags & 4)
295 scr_printf("Chat request ");
296 else
297 scr_printf("Message ");
299 /* Timestamp. Can this be improved? */
300 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
301 scr_printf("at 12:%02d%cm", stamp.tm_min,
302 stamp.tm_hour ? 'p' : 'a');
303 else if (stamp.tm_hour > 12) /* pm */
304 scr_printf("at %d:%02dpm",
305 stamp.tm_hour - 12, stamp.tm_min);
306 else /* am */
307 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
309 /* Sender */
310 scr_printf(" from %s", sender);
312 /* Remote node, if any */
313 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
314 scr_printf(" @%s", node);
316 scr_printf(":\n");
317 lines_printed++;
318 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
319 free(listing);
321 /* when running in curses mode, the scroll bar in most
322 xterm-style programs becomes useless, so it makes sense to
323 pause after a screenful of pages if the user has been idle
324 for a while. However, this is annoying to some of the users
325 who aren't in curses mode and tend to leave their clients
326 idle. keepalives become disabled, resulting in getting booted
327 when coming back to the idle session. but they probably have
328 a working scrollback in their terminal, so disable it in this
329 case:
331 if (!is_curses_enabled())
332 lines_printed = 0;
334 scr_printf("\n---\n");
335 color(BRIGHT_WHITE);
341 void set_keepalives(int s)
343 keepalives_enabled = (char) s;
347 * This loop handles the "keepalive" messages sent to the server when idling.
350 static time_t idlet = 0;
351 static void really_do_keepalive(void) {
352 int r; /* IPC response code */
354 time(&idlet);
356 /* This may sometimes get called before we are actually connected
357 * to the server. Don't do anything if we aren't connected. -IO
359 if (!ipc_for_signal_handlers)
360 return;
362 /* If full keepalives are enabled, send a NOOP to the server and
363 * wait for a response.
365 if (keepalives_enabled == KA_YES) {
366 r = CtdlIPCNoop(ipc_for_signal_handlers);
367 if (instant_msgs > 0) {
368 if (ok_to_interrupt == 1) {
369 scr_printf("\r%64s\r", "");
370 print_instant();
371 scr_printf("%s%c ", room_name,
372 room_prompt(room_flags));
373 scr_flush();
378 /* If half keepalives are enabled, send a QNOP to the server (if the
379 * server supports it) and then do nothing.
381 if ( (keepalives_enabled == KA_HALF)
382 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
383 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
387 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
388 encapsulated interface; in theory there should be no need to touch these
389 globals outside of the async_ka_* functions. */
391 #ifdef THREADED_CLIENT
392 static pthread_t ka_thr_handle;
393 static int ka_thr_active = 0;
394 static int async_ka_enabled = 0;
396 static void *ka_thread(void *arg)
398 #ifdef HAVE_BACKTRACE
399 char threadName[256];
401 // Set up our name
402 sprintf(threadName, "ka_Thread n");
404 // Register for tracing
405 eCrash_RegisterThread(threadName, 0);
406 #endif
407 really_do_keepalive();
408 pthread_detach(ka_thr_handle);
409 ka_thr_active = 0;
411 #ifdef HAVE_BACKTRACE
412 eCrash_UnregisterThread();
413 #endif
414 return NULL;
417 /* start up a thread to handle a keepalive in the background */
418 static void async_ka_exec(void)
420 if (!ka_thr_active) {
421 ka_thr_active = 1;
422 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
423 perror("pthread_create");
424 exit(1);
428 #endif /* THREADED_CLIENT */
430 /* I changed this from static to not because I need to call it from
431 screen.c, either that or make something in screen.c not static.
432 Fix it how you like. Why all the staticness? stu */
434 void do_keepalive(void)
436 time_t now;
438 time(&now);
439 if ((now - idlet) < ((long) S_KEEPALIVE))
440 return;
442 /* Do a space-backspace to keep telnet sessions from idling out */
443 scr_printf(" %c", 8);
444 scr_flush();
446 #ifdef THREADED_CLIENT
447 if (async_ka_enabled)
448 async_ka_exec();
449 else
450 #endif
451 really_do_keepalive();
455 /* Now the actual async-keepalve API that we expose to higher levels:
456 async_ka_start() and async_ka_end(). These do nothing when we don't have
457 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
459 /* wait for a background keepalive to complete. this must be done before
460 attempting any further server requests! */
461 void async_ka_end(void)
463 #ifdef THREADED_CLIENT
464 if (ka_thr_active)
465 pthread_join(ka_thr_handle, NULL);
467 async_ka_enabled--;
468 #endif
471 /* tell do_keepalive() that keepalives are asynchronous. */
472 void async_ka_start(void)
474 #ifdef THREADED_CLIENT
475 async_ka_enabled++;
476 #endif
480 int inkey(void)
481 { /* get a character from the keyboard, with */
482 int a; /* the watchdog timer in effect if necessary */
483 fd_set rfds;
484 struct timeval tv;
485 time_t start_time;
487 scr_flush();
488 lines_printed = 0;
489 time(&start_time);
491 do {
492 /* This loop waits for keyboard input. If the keepalive
493 * timer expires, it sends a keepalive to the server if
494 * necessary and then waits again.
496 do {
497 scr_set_windowsize(ipc_for_signal_handlers);
498 do_keepalive();
499 scr_set_windowsize(ipc_for_signal_handlers);
501 FD_ZERO(&rfds);
502 FD_SET(0, &rfds);
503 tv.tv_sec = S_KEEPALIVE;
504 tv.tv_usec = 0;
506 select(1, &rfds, NULL, NULL, &tv);
507 } while (!FD_ISSET(0, &rfds));
509 /* At this point, there's input, so fetch it.
510 * (There's a hole in the bucket...)
512 a = scr_getc(SCR_BLOCK);
513 if (a == 127) {
514 a = 8;
516 if (a == 13) {
517 a = 10;
519 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
520 if (a > 126) {
521 a = 0;
523 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
524 && ((a < 32) || (a > 126))) {
525 a = 0;
529 #ifndef DISABLE_CURSES
530 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
531 if (a == ERR) {
532 logoff(NULL, 3);
534 #endif
535 #endif
537 } while (a == 0);
538 return (a);
542 int yesno(void)
543 { /* Returns 1 for yes, 0 for no */
544 int a;
545 while (1) {
546 a = inkey();
547 a = tolower(a);
548 if (a == 'y') {
549 scr_printf("Yes\n");
550 return (1);
552 if (a == 'n') {
553 scr_printf("No\n");
554 return (0);
559 /* Returns 1 for yes, 0 for no, arg is default value */
560 int yesno_d(int d)
562 int a;
563 while (1) {
564 a = inkey();
565 a = tolower(a);
566 if (a == 10)
567 a = (d ? 'y' : 'n');
568 if (a == 'y') {
569 scr_printf("Yes\n");
570 return (1);
572 if (a == 'n') {
573 scr_printf("No\n");
574 return (0);
582 /* Gets a line from the terminal */
583 /* string == Pointer to string buffer */
584 /* lim == Maximum length - if negative, no-show */
585 void ctdl_getline(char *string, int lim)
587 int a, b;
588 char flag = 0;
590 if (lim < 0) {
591 lim = (0 - lim);
592 flag = 1;
594 strcpy(string, "");
595 gl_string = string;
596 async_ka_start();
598 GLA: a = inkey();
599 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
600 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
601 goto GLA;
602 if ((a != 10) && (a != 8) && (strlen(string) == lim))
603 goto GLA;
604 if ((a == 8) && (string[0] != 0)) {
605 string[strlen(string) - 1] = 0;
606 scr_putc(8); scr_putc(32); scr_putc(8);
607 goto GLA;
609 if ((a == 23) && (string[0] != 0)) {
610 do {
611 string[strlen(string) - 1] = 0;
612 scr_putc(8); scr_putc(32); scr_putc(8);
613 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
614 goto GLA;
616 if ((a == 10)) {
617 scr_putc(10);
618 async_ka_end();
619 return;
621 if (a < 32)
622 a = '.';
623 b = strlen(string);
624 string[b] = a;
625 string[b + 1] = 0;
626 if (flag == 0)
627 scr_putc(a);
628 if (flag == 1)
629 scr_putc('*');
630 goto GLA;
635 * strprompt() - prompt for a string, print the existing value and
636 * allow the user to press return to keep it...
638 void strprompt(char *prompt, char *str, int len)
640 int i;
641 char buf[128];
643 print_instant();
644 color(DIM_WHITE);
645 scr_printf("%s ", prompt);
646 color(DIM_MAGENTA);
647 scr_printf("[");
648 color(BRIGHT_MAGENTA);
650 if (len >= 0) {
651 scr_printf("%s", str);
653 else {
654 for (i=0; !IsEmptyStr(&str[i]); ++i) {
655 scr_printf("*");
659 color(DIM_MAGENTA);
660 scr_printf("]");
661 color(DIM_WHITE);
662 scr_printf(": ");
663 color(BRIGHT_CYAN);
664 ctdl_getline(buf, len);
665 if (buf[0] != 0)
666 strcpy(str, buf);
667 color(DIM_WHITE);
671 * boolprompt() - prompt for a yes/no, print the existing value and
672 * allow the user to press return to keep it...
674 int boolprompt(char *prompt, int prev_val)
676 int r;
678 color(DIM_WHITE);
679 scr_printf("%s ", prompt);
680 color(DIM_MAGENTA);
681 scr_printf("[");
682 color(BRIGHT_MAGENTA);
683 scr_printf("%s", (prev_val ? "Yes" : "No"));
684 color(DIM_MAGENTA);
685 scr_printf("]: ");
686 color(BRIGHT_CYAN);
687 r = (yesno_d(prev_val));
688 color(DIM_WHITE);
689 return r;
693 * intprompt() - like strprompt(), except for an integer
694 * (note that it RETURNS the new value!)
696 int intprompt(char *prompt, int ival, int imin, int imax)
698 char buf[16];
699 int i;
700 int p;
702 do {
703 i = ival;
704 snprintf(buf, sizeof buf, "%d", i);
705 strprompt(prompt, buf, 15);
706 i = atoi(buf);
707 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
708 if ( (!isdigit(buf[p]))
709 && ( (buf[p]!='-') || (p!=0) ) )
710 i = imin - 1;
712 if (i < imin)
713 scr_printf("*** Must be no less than %d.\n", imin);
714 if (i > imax)
715 scr_printf("*** Must be no more than %d.\n", imax);
716 } while ((i < imin) || (i > imax));
717 return (i);
721 * newprompt() - prompt for a string with no existing value
722 * (clears out string buffer first)
724 void newprompt(char *prompt, char *str, int len)
726 color(BRIGHT_MAGENTA);
727 scr_printf("%s", prompt);
728 color(DIM_MAGENTA);
729 ctdl_getline(str, len);
730 color(DIM_WHITE);
734 int lkey(void)
735 { /* returns a lower case value */
736 int a;
737 a = inkey();
738 if (isupper(a))
739 a = tolower(a);
740 return (a);
744 * parse the citadel.rc file
746 void load_command_set(void)
748 FILE *ccfile;
749 char buf[1024];
750 char editor_key[100];
751 struct citcmd *cptr;
752 struct citcmd *lastcmd = NULL;
753 int a, d;
754 int b = 0;
755 int i;
758 /* first, set up some defaults for non-required variables */
760 for (i = 0; i < MAX_EDITORS; i++)
761 strcpy(editor_paths[i], "");
762 strcpy(printcmd, "");
763 strcpy(imagecmd, "");
764 strcpy(rc_username, "");
765 strcpy(rc_password, "");
766 rc_floor_mode = 0;
767 rc_exp_beep = 1;
768 rc_allow_attachments = 0;
769 rc_remember_passwords = 0;
770 strcpy(rc_exp_cmd, "");
771 rc_display_message_numbers = 0;
772 rc_force_mail_prompts = 0;
773 rc_ansi_color = 0;
774 rc_color_use_bg = 0;
775 strcpy(rc_url_cmd, "");
776 strcpy(rc_open_cmd, "");
777 strcpy(rc_gotmail_cmd, "");
778 #ifdef HAVE_OPENSSL
779 rc_encrypt = RC_DEFAULT;
780 #endif
781 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
782 rc_screen = RC_DEFAULT;
783 #endif
784 rc_alt_semantics = 0;
786 /* now try to open the citadel.rc file */
788 ccfile = NULL;
789 if (getenv("HOME") != NULL) {
790 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
791 ccfile = fopen(buf, "r");
793 if (ccfile == NULL) {
794 ccfile = fopen(file_citadel_rc, "r");
796 if (ccfile == NULL) {
797 ccfile = fopen("/etc/citadel.rc", "r");
799 if (ccfile == NULL) {
800 ccfile = fopen("./citadel.rc", "r");
802 if (ccfile == NULL) {
803 perror("commands: cannot open citadel.rc");
804 logoff(NULL, 3);
806 while (fgets(buf, sizeof buf, ccfile) != NULL) {
807 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
808 buf[strlen(buf) - 1] = 0;
810 if (!strncasecmp(buf, "encrypt=", 8)) {
811 if (!strcasecmp(&buf[8], "yes")) {
812 #ifdef HAVE_OPENSSL
813 rc_encrypt = RC_YES;
814 #else
815 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
816 logoff(NULL, 3);
817 #endif
819 #ifdef HAVE_OPENSSL
820 else if (!strcasecmp(&buf[8], "no")) {
821 rc_encrypt = RC_NO;
823 else if (!strcasecmp(&buf[8], "default")) {
824 rc_encrypt = RC_DEFAULT;
826 #endif
829 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
830 if (!strncasecmp(buf, "fullscreen=", 11)) {
831 if (!strcasecmp(&buf[11], "yes"))
832 rc_screen = RC_YES;
833 else if (!strcasecmp(&buf[11], "no"))
834 rc_screen = RC_NO;
836 #endif
838 if (!strncasecmp(buf, "editor=", 7))
839 strcpy(editor_paths[0], &buf[7]);
841 for (i = 0; i < MAX_EDITORS; i++)
843 sprintf(editor_key, "editor%d=", i);
844 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
845 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
848 if (!strncasecmp(buf, "printcmd=", 9))
849 strcpy(printcmd, &buf[9]);
851 if (!strncasecmp(buf, "imagecmd=", 9))
852 strcpy(imagecmd, &buf[9]);
854 if (!strncasecmp(buf, "expcmd=", 7))
855 strcpy(rc_exp_cmd, &buf[7]);
857 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
858 have_xterm = (char) atoi(&buf[24]);
860 if (!strncasecmp(buf, "use_floors=", 11)) {
861 if (!strcasecmp(&buf[11], "yes"))
862 rc_floor_mode = RC_YES;
863 if (!strcasecmp(&buf[11], "no"))
864 rc_floor_mode = RC_NO;
865 if (!strcasecmp(&buf[11], "default"))
866 rc_floor_mode = RC_DEFAULT;
868 if (!strncasecmp(buf, "beep=", 5)) {
869 rc_exp_beep = atoi(&buf[5]);
871 if (!strncasecmp(buf, "allow_attachments=", 18)) {
872 rc_allow_attachments = atoi(&buf[18]);
874 if (!strncasecmp(buf, "idle_threshold=", 15)) {
875 rc_idle_threshold = atol(&buf[15]);
877 if (!strncasecmp(buf, "remember_passwords=", 19)) {
878 rc_remember_passwords = atoi(&buf[19]);
880 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
881 rc_display_message_numbers = atoi(&buf[24]);
883 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
884 rc_force_mail_prompts = atoi(&buf[19]);
886 if (!strncasecmp(buf, "ansi_color=", 11)) {
887 if (!strncasecmp(&buf[11], "on", 2))
888 rc_ansi_color = 1;
889 if (!strncasecmp(&buf[11], "auto", 4))
890 rc_ansi_color = 2; /* autodetect */
891 if (!strncasecmp(&buf[11], "user", 4))
892 rc_ansi_color = 3; /* user config */
894 if (!strncasecmp(buf, "use_background=", 15)) {
895 if (!strncasecmp(&buf[15], "on", 2))
896 rc_color_use_bg = 9;
898 if (!strncasecmp(buf, "prompt_control=", 15)) {
899 if (!strncasecmp(&buf[15], "on", 2))
900 rc_prompt_control = 1;
901 if (!strncasecmp(&buf[15], "user", 4))
902 rc_prompt_control = 3; /* user config */
904 if (!strncasecmp(buf, "username=", 9))
905 strcpy(rc_username, &buf[9]);
907 if (!strncasecmp(buf, "password=", 9))
908 strcpy(rc_password, &buf[9]);
910 if (!strncasecmp(buf, "urlcmd=", 7))
911 strcpy(rc_url_cmd, &buf[7]);
913 if (!strncasecmp(buf, "opencmd=", 7))
914 strcpy(rc_open_cmd, &buf[8]);
916 if (!strncasecmp(buf, "gotmailcmd=", 11))
917 strcpy(rc_gotmail_cmd, &buf[11]);
919 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
920 if (!strncasecmp(&buf[20], "yes", 3)) {
921 rc_alt_semantics = 1;
923 else {
924 rc_alt_semantics = 0;
928 if (!strncasecmp(buf, "cmd=", 4)) {
929 strcpy(buf, &buf[4]);
931 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
933 cptr->c_cmdnum = atoi(buf);
934 for (d = strlen(buf); d >= 0; --d)
935 if (buf[d] == ',')
936 b = d;
937 strcpy(buf, &buf[b + 1]);
939 cptr->c_axlevel = atoi(buf);
940 for (d = strlen(buf); d >= 0; --d)
941 if (buf[d] == ',')
942 b = d;
943 strcpy(buf, &buf[b + 1]);
945 for (a = 0; a < 5; ++a)
946 cptr->c_keys[a][0] = 0;
948 a = 0;
949 b = 0;
950 buf[strlen(buf) + 1] = 0;
951 while (!IsEmptyStr(buf)) {
952 b = strlen(buf);
953 for (d = strlen(buf); d >= 0; --d)
954 if (buf[d] == ',')
955 b = d;
956 strncpy(cptr->c_keys[a], buf, b);
957 cptr->c_keys[a][b] = 0;
958 if (buf[b] == ',')
959 strcpy(buf, &buf[b + 1]);
960 else
961 strcpy(buf, "");
962 ++a;
965 cptr->next = NULL;
966 if (cmdlist == NULL)
967 cmdlist = cptr;
968 else
969 lastcmd->next = cptr;
970 lastcmd = cptr;
973 fclose(ccfile);
979 * return the key associated with a command
981 char keycmd(char *cmdstr)
983 int a;
985 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
986 if (cmdstr[a] == '&')
987 return (tolower(cmdstr[a + 1]));
988 return (0);
993 * Output the string from a key command without the ampersand
994 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
996 char *cmd_expand(char *strbuf, int mode)
998 int a;
999 static char exp[64];
1000 char buf[1024];
1002 strcpy(exp, strbuf);
1004 for (a = 0; exp[a]; ++a) {
1005 if (strbuf[a] == '&') {
1007 /* dont echo these non mnemonic command keys */
1008 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1010 if (mode == 0) {
1011 strcpy(&exp[a], &exp[a + 1 + noecho]);
1013 if (mode == 1) {
1014 exp[a] = '<';
1015 strcpy(buf, &exp[a + 2]);
1016 exp[a + 2] = '>';
1017 exp[a + 3] = 0;
1018 strcat(exp, buf);
1021 if (!strncmp(&exp[a], "^r", 2)) {
1022 strcpy(buf, exp);
1023 strcpy(&exp[a], room_name);
1024 strcat(exp, &buf[a + 2]);
1026 if (!strncmp(&exp[a], "^c", 2)) {
1027 exp[a] = ',';
1028 strcpy(&exp[a + 1], &exp[a + 2]);
1032 return (exp);
1038 * Comparison function to determine if entered commands match a
1039 * command loaded from the config file.
1041 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1043 int a;
1044 int cmdax;
1046 cmdax = 0;
1047 if (is_room_aide)
1048 cmdax = 1;
1049 if (axlevel >= 6)
1050 cmdax = 2;
1052 for (a = 0; a < ncomp; ++a) {
1053 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1054 || (cptr->c_axlevel > cmdax))
1055 return (0);
1057 return (1);
1062 * This function returns 1 if a given command requires a string input
1064 int requires_string(struct citcmd *cptr, int ncomp)
1066 int a;
1067 char buf[64];
1069 strcpy(buf, cptr->c_keys[ncomp - 1]);
1070 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1071 if (buf[a] == ':')
1072 return (1);
1074 return (0);
1079 * Input a command at the main prompt.
1080 * This function returns an integer command number. If the command prompts
1081 * for a string then it is placed in the supplied buffer.
1083 int getcmd(CtdlIPC *ipc, char *argbuf)
1085 char cmdbuf[5];
1086 int cmdspaces[5];
1087 int cmdpos;
1088 int ch;
1089 int a;
1090 int got;
1091 int this_lazy_cmd;
1092 struct citcmd *cptr;
1095 * Starting a new command now, so set sigcaught to 0. This variable
1096 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1097 * been interrupted by a keypress.
1099 sigcaught = 0;
1101 /* Switch color support on or off if we're in user mode */
1102 if (rc_ansi_color == 3) {
1103 if (userflags & US_COLOR)
1104 enable_color = 1;
1105 else
1106 enable_color = 0;
1108 /* if we're running in idiot mode, display a cute little menu */
1109 IFNEXPERT formout(ipc, "mainmenu");
1111 print_instant();
1112 strcpy(argbuf, "");
1113 cmdpos = 0;
1114 for (a = 0; a < 5; ++a)
1115 cmdbuf[a] = 0;
1116 /* now the room prompt... */
1117 ok_to_interrupt = 1;
1118 color(BRIGHT_WHITE);
1119 scr_printf("\n%s", room_name);
1120 color(DIM_WHITE);
1121 scr_printf("%c ", room_prompt(room_flags));
1122 scr_flush();
1124 while (1) {
1125 ch = inkey();
1126 ok_to_interrupt = 0;
1128 /* Handle the backspace key, but only if there's something
1129 * to backspace over...
1131 if ((ch == 8) && (cmdpos > 0)) {
1132 back(cmdspaces[cmdpos - 1] + 1);
1133 cmdbuf[cmdpos] = 0;
1134 --cmdpos;
1136 /* Spacebar invokes "lazy traversal" commands */
1137 if ((ch == 32) && (cmdpos == 0)) {
1138 this_lazy_cmd = next_lazy_cmd;
1139 if (this_lazy_cmd == 13)
1140 next_lazy_cmd = 5;
1141 if (this_lazy_cmd == 5)
1142 next_lazy_cmd = 13;
1143 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1144 if (cptr->c_cmdnum == this_lazy_cmd) {
1145 for (a = 0; a < 5; ++a)
1146 if (cptr->c_keys[a][0] != 0)
1147 scr_printf("%s ", cmd_expand(
1148 cptr->c_keys[a], 0));
1149 scr_printf("\n");
1150 return (this_lazy_cmd);
1153 scr_printf("\n");
1154 return (this_lazy_cmd);
1156 /* Otherwise, process the command */
1157 cmdbuf[cmdpos] = tolower(ch);
1159 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1160 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1162 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1163 cmdspaces[cmdpos] = strlen(
1164 cmd_expand(cptr->c_keys[cmdpos], 0));
1165 if (cmdpos < 4)
1166 if ((cptr->c_keys[cmdpos + 1]) != 0)
1167 scr_putc(' ');
1168 ++cmdpos;
1172 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1173 if (cmdmatch(cmdbuf, cptr, 5)) {
1174 /* We've found our command. */
1175 if (requires_string(cptr, cmdpos)) {
1176 ctdl_getline(argbuf, 64);
1177 } else {
1178 scr_printf("\n");
1181 /* If this command is one that changes rooms,
1182 * then the next lazy-command (space bar)
1183 * should be "read new" instead of "goto"
1185 if ((cptr->c_cmdnum == 5)
1186 || (cptr->c_cmdnum == 6)
1187 || (cptr->c_cmdnum == 47)
1188 || (cptr->c_cmdnum == 52)
1189 || (cptr->c_cmdnum == 16)
1190 || (cptr->c_cmdnum == 20))
1191 next_lazy_cmd = 13;
1193 /* If this command is "read new"
1194 * then the next lazy-command (space bar)
1195 * should be "goto"
1197 if (cptr->c_cmdnum == 13)
1198 next_lazy_cmd = 5;
1200 return (cptr->c_cmdnum);
1205 if (ch == '?') {
1206 pprintf("\rOne of ... \n");
1207 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1208 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1209 for (a = 0; a < 5; ++a) {
1210 keyopt(cmd_expand(cptr->c_keys[a], 1));
1211 pprintf(" ");
1213 pprintf("\n");
1216 sigcaught = 0;
1218 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1219 got = 0;
1220 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1221 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1222 for (a = 0; a < cmdpos; ++a) {
1223 pprintf("%s ",
1224 cmd_expand(cptr->c_keys[a], 0));
1226 got = 1;
1239 * set tty modes. commands are:
1241 * 01- set to Citadel mode
1242 * 2 - save current settings for later restoral
1243 * 3 - restore saved settings
1245 #ifdef HAVE_TERMIOS_H
1246 void stty_ctdl(int cmd)
1247 { /* SysV version of stty_ctdl() */
1248 struct termios live;
1249 static struct termios saved_settings;
1250 static int last_cmd = 0;
1252 if (cmd == SB_LAST)
1253 cmd = last_cmd;
1254 else
1255 last_cmd = cmd;
1257 if ((cmd == 0) || (cmd == 1)) {
1258 tcgetattr(0, &live);
1259 live.c_iflag = ISTRIP | IXON | IXANY;
1260 live.c_oflag = OPOST | ONLCR;
1261 live.c_lflag = ISIG | NOFLSH;
1263 live.c_cc[VINTR] = 0;
1264 live.c_cc[VQUIT] = 0;
1266 #ifdef hpux
1267 live.c_cc[VMIN] = 0;
1268 live.c_cc[VTIME] = 0;
1269 #endif
1271 /* do we even need this stuff anymore? */
1272 /* live.c_line=0; */
1273 live.c_cc[VERASE] = 8;
1274 live.c_cc[VKILL] = 24;
1275 live.c_cc[VEOF] = 1;
1276 live.c_cc[VEOL] = 255;
1277 live.c_cc[VEOL2] = 0;
1278 live.c_cc[VSTART] = 0;
1279 tcsetattr(0, TCSADRAIN, &live);
1281 if (cmd == 2) {
1282 tcgetattr(0, &saved_settings);
1284 if (cmd == 3) {
1285 tcsetattr(0, TCSADRAIN, &saved_settings);
1289 #else
1290 void stty_ctdl(int cmd)
1291 { /* BSD version of stty_ctdl() */
1292 struct sgttyb live;
1293 static struct sgttyb saved_settings;
1294 static int last_cmd = 0;
1296 if (cmd == SB_LAST)
1297 cmd = last_cmd;
1298 else
1299 last_cmd = cmd;
1301 if ((cmd == 0) || (cmd == 1)) {
1302 gtty(0, &live);
1303 live.sg_flags |= CBREAK;
1304 live.sg_flags |= CRMOD;
1305 live.sg_flags |= NL1;
1306 live.sg_flags &= ~ECHO;
1307 if (cmd == 1)
1308 live.sg_flags |= NOFLSH;
1309 stty(0, &live);
1311 if (cmd == 2) {
1312 gtty(0, &saved_settings);
1314 if (cmd == 3) {
1315 stty(0, &saved_settings);
1318 #endif
1322 * display_help() - help file viewer
1324 void display_help(CtdlIPC *ipc, char *name)
1326 formout(ipc, name);
1331 * fmout() - Citadel text formatter and paginator
1333 int fmout(
1334 int width, /* screen width to use */
1335 FILE *fpin, /* file to read from, or NULL to format given text */
1336 char *text, /* text to be formatted (when fpin is NULL */
1337 FILE *fpout, /* file to write to, or NULL to write to screen */
1338 char pagin, /* nonzero if we should use the paginator */
1339 int height, /* screen height to use */
1340 int starting_lp,/* starting value for lines_printed, -1 for global */
1341 int subst) /* nonzero if we should use hypertext mode */
1343 char *buffer = NULL; /* The current message */
1344 char *word = NULL; /* What we are about to actually print */
1345 char *e; /* Pointer to position in text */
1346 char old = 0; /* The previous character */
1347 int column = 0; /* Current column */
1348 size_t i; /* Generic counter */
1350 /* Space for a single word, which can be at most screenwidth */
1351 word = (char *)calloc(1, width);
1352 if (!word) {
1353 err_printf("Can't alloc memory to print message: %s!\n",
1354 strerror(errno));
1355 logoff(NULL, 3);
1358 /* Read the entire message body into memory */
1359 if (fpin) {
1360 buffer = load_message_from_file(fpin);
1361 if (!buffer) {
1362 err_printf("Can't print message: %s!\n",
1363 strerror(errno));
1364 logoff(NULL, 3);
1366 } else {
1367 buffer = text;
1369 e = buffer;
1371 if (starting_lp >= 0)
1372 lines_printed = starting_lp;
1374 /* Run the message body */
1375 while (*e) {
1376 /* Catch characters that shouldn't be there at all */
1377 if (*e == '\r') {
1378 e++;
1379 continue;
1381 /* First, are we looking at a newline? */
1382 if (*e == '\n') {
1383 e++;
1384 if (*e == ' ') { /* Paragraph */
1385 if (fpout) {
1386 fprintf(fpout, "\n");
1387 } else {
1388 scr_printf("\n");
1389 ++lines_printed;
1390 lines_printed = checkpagin(lines_printed, pagin, height);
1392 column = 0;
1393 } else if (old != ' ') {/* Don't print two spaces */
1394 if (fpout) {
1395 fprintf(fpout, " ");
1396 } else {
1397 scr_printf(" ");
1399 column++;
1401 old = '\n';
1402 continue;
1405 /* Are we looking at a nonprintable?
1406 * (This section is now commented out because we could be displaying
1407 * a character set like UTF-8 or ISO-8859-1.)
1408 if ( (*e < 32) || (*e > 126) ) {
1409 e++;
1410 continue;
1411 } */
1413 /* Or are we looking at a space? */
1414 if (*e == ' ') {
1415 e++;
1416 if (column >= width - 1) {
1417 /* Are we in the rightmost column? */
1418 if (fpout) {
1419 fprintf(fpout, "\n");
1420 } else {
1421 scr_printf("\n");
1422 ++lines_printed;
1423 lines_printed = checkpagin(lines_printed, pagin, height);
1425 column = 0;
1426 } else if (!(column == 0 && old == ' ')) {
1427 /* Eat only the first space on a line */
1428 if (fpout) {
1429 fprintf(fpout, " ");
1430 } else {
1431 scr_printf(" ");
1433 column++;
1435 /* ONLY eat the FIRST space on a line */
1436 old = ' ';
1437 continue;
1439 old = *e;
1441 /* Read a word, slightly messy */
1442 i = 0;
1443 while (e[i]) {
1444 if (!isprint(e[i]) && !isspace(e[i]))
1445 e[i] = ' ';
1446 if (isspace(e[i]))
1447 break;
1448 i++;
1451 /* We should never see these, but... slightly messy */
1452 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1453 e[i] = ' ';
1455 /* Break up really long words */
1456 /* TODO: auto-hyphenation someday? */
1457 if (i >= width)
1458 i = width - 1;
1459 strncpy(word, e, i);
1460 word[i] = 0;
1462 /* Decide where to print the word */
1463 if (column + i >= width) {
1464 /* Wrap to the next line */
1465 if (fpout) {
1466 fprintf(fpout, "\n");
1467 } else {
1468 scr_printf("\n");
1469 ++lines_printed;
1470 lines_printed = checkpagin(lines_printed, pagin, height);
1472 column = 0;
1475 /* Print the word */
1476 if (fpout) {
1477 fprintf(fpout, "%s", word);
1478 } else {
1479 scr_printf("%s", word);
1481 column += i;
1482 e += i; /* Start over with the whitepsace! */
1485 free(word);
1486 if (fpin) /* We allocated this, remember? */
1487 free(buffer);
1489 /* Is this necessary? It makes the output kind of spacey. */
1490 if (fpout) {
1491 fprintf(fpout, "\n");
1492 } else {
1493 scr_printf("\n");
1494 ++lines_printed;
1495 lines_printed = checkpagin(lines_printed, pagin, height);
1498 return sigcaught;
1503 * support ANSI color if defined
1505 void color(int colornum)
1507 static int hold_color;
1508 static int current_color;
1510 if (colornum == COLOR_PUSH) {
1511 hold_color = current_color;
1512 return;
1515 if (colornum == COLOR_POP) {
1516 color(hold_color);
1517 return;
1520 current_color = colornum;
1521 if (enable_color) {
1522 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1523 if (scr_color(colornum))
1524 return;
1525 #endif
1526 /* When switching to dim white, actually output an 'original
1527 * pair' sequence -- this looks better on black-on-white
1528 * terminals. - Changed to ORIGINAL_PAIR as this actually
1529 * wound up looking horrible on black-on-white terminals, not
1530 * to mention transparent terminals.
1532 if (colornum == ORIGINAL_PAIR)
1533 printf("\033[0;39;49m");
1534 else
1535 printf("\033[%d;3%d;4%dm",
1536 (colornum & 8) ? 1 : 0,
1537 (colornum & 7),
1538 rc_color_use_bg);
1540 scr_flush();
1544 void cls(int colornum)
1546 if (enable_color) {
1547 printf("\033[4%dm\033[2J\033[H\033[0m",
1548 colornum ? colornum : rc_color_use_bg);
1549 scr_flush();
1555 * Detect whether ANSI color is available (answerback)
1557 void send_ansi_detect(void)
1559 if (rc_ansi_color == 2) {
1560 printf("\033[c");
1561 scr_flush();
1562 time(&AnsiDetect);
1566 void look_for_ansi(void)
1568 fd_set rfds;
1569 struct timeval tv;
1570 char abuf[512];
1571 time_t now;
1572 int a;
1574 if (rc_ansi_color == 0) {
1575 enable_color = 0;
1576 } else if (rc_ansi_color == 1) {
1577 enable_color = 1;
1578 } else if (rc_ansi_color == 2) {
1580 /* otherwise, do the auto-detect */
1582 strcpy(abuf, "");
1584 time(&now);
1585 if ((now - AnsiDetect) < 2)
1586 sleep(1);
1588 do {
1589 FD_ZERO(&rfds);
1590 FD_SET(0, &rfds);
1591 tv.tv_sec = 0;
1592 tv.tv_usec = 1;
1594 select(1, &rfds, NULL, NULL, &tv);
1595 if (FD_ISSET(0, &rfds)) {
1596 abuf[strlen(abuf) + 1] = 0;
1597 read(0, &abuf[strlen(abuf)], 1);
1599 } while (FD_ISSET(0, &rfds));
1601 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1602 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1603 && (abuf[a + 2] == '?')) {
1604 enable_color = 1;
1612 * Display key options (highlight hotkeys inside angle brackets)
1614 void keyopt(char *buf) {
1615 int i;
1617 color(DIM_WHITE);
1618 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1619 if (buf[i]=='<') {
1620 pprintf("%c", buf[i]);
1621 color(BRIGHT_MAGENTA);
1622 } else {
1623 if (buf[i]=='>'&& buf[i+1] != '>') {
1624 color(DIM_WHITE);
1626 pprintf("%c", buf[i]);
1629 color(DIM_WHITE);
1635 * Present a key-menu line choice type of thing
1637 char keymenu(char *menuprompt, char *menustring) {
1638 int i, c, a;
1639 int choices;
1640 int do_prompt = 0;
1641 char buf[1024];
1642 int ch;
1643 int display_prompt = 1;
1645 choices = num_tokens(menustring, '|');
1647 if (menuprompt != NULL) do_prompt = 1;
1648 if ((menuprompt != NULL) && (IsEmptyStr(menuprompt))) do_prompt = 0;
1650 while (1) {
1651 if (display_prompt) {
1652 if (do_prompt) {
1653 scr_printf("%s ", menuprompt);
1655 else {
1656 for (i=0; i<choices; ++i) {
1657 extract_token(buf, menustring, i, '|', sizeof buf);
1658 keyopt(buf);
1659 scr_printf(" ");
1662 scr_printf("-> ");
1663 display_prompt = 0;
1665 ch = lkey();
1667 if ( (do_prompt) && (ch=='?') ) {
1668 scr_printf("\rOne of... ");
1669 scr_printf(" \n");
1670 for (i=0; i<choices; ++i) {
1671 extract_token(buf, menustring, i, '|', sizeof buf);
1672 scr_printf(" ");
1673 keyopt(buf);
1674 scr_printf("\n");
1676 scr_printf("\n");
1677 display_prompt = 1;
1680 for (i=0; i<choices; ++i) {
1681 extract_token(buf, menustring, i, '|', sizeof buf);
1682 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1683 if ( (ch == tolower(buf[c]))
1684 && (buf[c-1]=='<')
1685 && (buf[c+1]=='>') ) {
1686 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1687 if ( (a!=(c-1)) && (a!=(c+1))) {
1688 scr_putc(buf[a]);
1691 scr_printf("\n");
1692 return ch;