4 * This file contains functions which implement parts of the
5 * text-mode user interface.
16 #include <sys/types.h>
18 #if TIME_WITH_SYS_TIME
19 # include <sys/time.h>
23 # include <sys/time.h>
35 #ifdef HAVE_SYS_SELECT_H
36 #include <sys/select.h>
39 #ifdef THREADED_CLIENT
47 #include "citadel_ipc.h"
50 #include "citadel_decls.h"
52 #include "routines2.h"
55 #include "client_chat.h"
56 #include "citadel_dirs.h"
70 #define IFNEXPERT if ((userflags&US_EXPERT)==0)
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;
81 int rc_prompt_control = 0;
82 time_t rc_idle_threshold = (time_t)900;
84 char rc_gotmail_cmd[SIZ];
87 int next_lazy_cmd = 5;
89 int lines_printed = 0; /* line count for paginator */
90 extern int screenwidth, screenheight;
92 extern CtdlIPC *ipc_for_signal_handlers; /* KLUDGE cover your eyes */
94 struct citcmd *cmdlist = NULL;
97 /* these variables are local to this module */
98 char keepalives_enabled = KA_YES; /* send NOOPs to server when idle */
99 int ok_to_interrupt = 0; /* print instant msgs asynchronously */
100 time_t AnsiDetect; /* when did we send the detect code? */
101 int enable_color = 0; /* nonzero for ANSI color */
107 * If an interesting key has been pressed, return its value, otherwise 0
109 char was_a_key_pressed(void) {
119 retval = select(1, &rfds, NULL, NULL, &tv);
121 /* Careful! Disable keepalives during keyboard polling; we're probably
122 * in the middle of a data transfer from the server, in which case
123 * sending a NOOP would throw the client protocol out of sync.
125 if (FD_ISSET(0, &rfds)) {
126 set_keepalives(KA_NO);
127 the_character = inkey();
128 set_keepalives(KA_YES);
133 return(the_character);
141 * Check to see if we need to pause at the end of a screen.
142 * If we do, we have to switch to half keepalives during the pause because
143 * we are probably in the middle of a server operation and the NOOP command
144 * would confuse everything.
146 int checkpagin(int lp, unsigned int pagin, unsigned int height)
150 if (sigcaught) return(lp);
151 thekey = was_a_key_pressed();
152 if (thekey == 'q' || thekey == 'Q' || thekey == 's' || thekey == 'S')
154 if (thekey == 'n' || thekey == 'N')
156 if ( (thekey == NEXT_KEY) || (thekey == STOP_KEY)) sigcaught = thekey;
157 if (sigcaught) return(lp);
159 if (!pagin) return(0);
160 if (lp>=(height-1)) {
161 set_keepalives(KA_HALF);
162 hit_any_key(ipc_for_signal_handlers); /* Cheating -IO */
163 set_keepalives(KA_YES);
173 * pprintf() ... paginated version of printf()
175 void pprintf(const char *format, ...) {
177 static char buf[4096]; /* static for performance, change if needed */
180 /* If sigcaught is nonzero, a keypress has interrupted this and we
181 * should just drain output.
183 if (sigcaught) return;
185 /* Otherwise, start spewing... */
186 va_start(arg_ptr, format);
187 vsnprintf(buf, sizeof(buf), format, arg_ptr);
190 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
194 lines_printed = checkpagin(lines_printed,
195 (userflags & US_PAGINATOR),
204 * print_instant() - print instant messages if there are any
206 void print_instant(void)
215 char *listing = NULL;
216 int r; /* IPC result code */
218 if (instant_msgs == 0)
224 if (IsEmptyStr(rc_exp_cmd)) {
229 while (instant_msgs != 0) {
230 r = CtdlIPCGetInstantMessage(ipc_for_signal_handlers, &listing, buf);
234 instant_msgs = extract_int(buf, 0);
235 timestamp = extract_long(buf, 1);
236 flags = extract_int(buf, 2);
237 extract_token(sender, buf, 3, '|', sizeof sender);
238 extract_token(node, buf, 4, '|', sizeof node);
239 strcpy(last_paged, sender);
241 localtime_r(×tamp, &stamp);
243 /* If the page is a Logoff Request, honor it. */
249 if (!IsEmptyStr(rc_exp_cmd)) {
250 outpipe = popen(rc_exp_cmd, "w");
251 if (outpipe != NULL) {
252 /* Header derived from flags */
255 "Please log off now, as requested ");
257 fprintf(outpipe, "Broadcast message ");
259 fprintf(outpipe, "Chat request ");
261 fprintf(outpipe, "Message ");
262 /* Timestamp. Can this be improved? */
263 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)
264 fprintf(outpipe, "at 12:%02d%cm",
266 stamp.tm_hour ? 'p' : 'a');
267 else if (stamp.tm_hour > 12) /* pm */
268 fprintf(outpipe, "at %d:%02dpm",
272 fprintf(outpipe, "at %d:%02dam",
273 stamp.tm_hour, stamp.tm_min);
274 fprintf(outpipe, " from %s", sender);
275 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
276 fprintf(outpipe, " @%s", node);
277 fprintf(outpipe, ":\n%s\n", listing);
279 if (instant_msgs == 0)
284 /* fall back to built-in instant message display */
288 /* Header derived from flags */
290 scr_printf("Please log off now, as requested ");
292 scr_printf("Broadcast message ");
294 scr_printf("Chat request ");
296 scr_printf("Message ");
298 /* Timestamp. Can this be improved? */
299 if (stamp.tm_hour == 0 || stamp.tm_hour == 12)/* 12am/12pm */
300 scr_printf("at 12:%02d%cm", stamp.tm_min,
301 stamp.tm_hour ? 'p' : 'a');
302 else if (stamp.tm_hour > 12) /* pm */
303 scr_printf("at %d:%02dpm",
304 stamp.tm_hour - 12, stamp.tm_min);
306 scr_printf("at %d:%02dam", stamp.tm_hour, stamp.tm_min);
309 scr_printf(" from %s", sender);
311 /* Remote node, if any */
312 if (strncmp(ipc_for_signal_handlers->ServInfo.nodename, node, 32))
313 scr_printf(" @%s", node);
317 fmout(screenwidth, NULL, listing, NULL, 1, screenheight, -1, 0);
320 /* when running in curses mode, the scroll bar in most
321 xterm-style programs becomes useless, so it makes sense to
322 pause after a screenful of pages if the user has been idle
323 for a while. However, this is annoying to some of the users
324 who aren't in curses mode and tend to leave their clients
325 idle. keepalives become disabled, resulting in getting booted
326 when coming back to the idle session. but they probably have
327 a working scrollback in their terminal, so disable it in this
330 if (!is_curses_enabled())
333 scr_printf("\n---\n");
340 void set_keepalives(int s)
342 keepalives_enabled = (char) s;
346 * This loop handles the "keepalive" messages sent to the server when idling.
349 static time_t idlet = 0;
350 static void really_do_keepalive(void) {
351 int r; /* IPC response code */
355 /* This may sometimes get called before we are actually connected
356 * to the server. Don't do anything if we aren't connected. -IO
358 if (!ipc_for_signal_handlers)
361 /* If full keepalives are enabled, send a NOOP to the server and
362 * wait for a response.
364 if (keepalives_enabled == KA_YES) {
365 r = CtdlIPCNoop(ipc_for_signal_handlers);
366 if (instant_msgs > 0) {
367 if (ok_to_interrupt == 1) {
368 scr_printf("\r%64s\r", "");
370 scr_printf("%s%c ", room_name,
371 room_prompt(room_flags));
377 /* If half keepalives are enabled, send a QNOP to the server (if the
378 * server supports it) and then do nothing.
380 if ( (keepalives_enabled == KA_HALF)
381 && (ipc_for_signal_handlers->ServInfo.supports_qnop > 0) ) {
382 CtdlIPC_chat_send(ipc_for_signal_handlers, "QNOP");
386 /* threaded nonblocking keepalive stuff starts here. I'm going for a simple
387 encapsulated interface; in theory there should be no need to touch these
388 globals outside of the async_ka_* functions. */
390 #ifdef THREADED_CLIENT
391 static pthread_t ka_thr_handle;
392 static int ka_thr_active = 0;
393 static int async_ka_enabled = 0;
395 static void *ka_thread(void *arg)
397 char threadName[256];
400 sprintf(threadName, "ka_Thread n");
402 // Register for tracing
403 eCrash_RegisterThread(threadName, 0);
405 really_do_keepalive();
406 pthread_detach(ka_thr_handle);
409 eCrash_UnregisterThread();
413 /* start up a thread to handle a keepalive in the background */
414 static void async_ka_exec(void)
416 if (!ka_thr_active) {
418 if (pthread_create(&ka_thr_handle, NULL, ka_thread, NULL)) {
419 perror("pthread_create");
424 #endif /* THREADED_CLIENT */
426 /* I changed this from static to not because I need to call it from
427 screen.c, either that or make something in screen.c not static.
428 Fix it how you like. Why all the staticness? stu */
430 void do_keepalive(void)
435 if ((now - idlet) < ((long) S_KEEPALIVE))
438 /* Do a space-backspace to keep telnet sessions from idling out */
439 scr_printf(" %c", 8);
442 #ifdef THREADED_CLIENT
443 if (async_ka_enabled)
447 really_do_keepalive();
451 /* Now the actual async-keepalve API that we expose to higher levels:
452 async_ka_start() and async_ka_end(). These do nothing when we don't have
453 threading enabled, so we avoid sprinkling ifdef's throughout the code. */
455 /* wait for a background keepalive to complete. this must be done before
456 attempting any further server requests! */
457 void async_ka_end(void)
459 #ifdef THREADED_CLIENT
461 pthread_join(ka_thr_handle, NULL);
467 /* tell do_keepalive() that keepalives are asynchronous. */
468 void async_ka_start(void)
470 #ifdef THREADED_CLIENT
477 { /* get a character from the keyboard, with */
478 int a; /* the watchdog timer in effect if necessary */
488 /* This loop waits for keyboard input. If the keepalive
489 * timer expires, it sends a keepalive to the server if
490 * necessary and then waits again.
493 scr_set_windowsize(ipc_for_signal_handlers);
495 scr_set_windowsize(ipc_for_signal_handlers);
499 tv.tv_sec = S_KEEPALIVE;
502 select(1, &rfds, NULL, NULL, &tv);
503 } while (!FD_ISSET(0, &rfds));
505 /* At this point, there's input, so fetch it.
506 * (There's a hole in the bucket...)
508 a = scr_getc(SCR_BLOCK);
515 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
519 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
520 && ((a < 32) || (a > 126))) {
525 #ifndef DISABLE_CURSES
526 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
539 { /* Returns 1 for yes, 0 for no */
555 /* Returns 1 for yes, 0 for no, arg is default value */
578 /* Gets a line from the terminal */
579 /* string == Pointer to string buffer */
580 /* lim == Maximum length - if negative, no-show */
581 void ctdl_getline(char *string, int lim)
595 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
596 if ((a == 8 || a == 23) && (IsEmptyStr(string)))
598 if ((a != 10) && (a != 8) && (strlen(string) == lim))
600 if ((a == 8) && (string[0] != 0)) {
601 string[strlen(string) - 1] = 0;
602 scr_putc(8); scr_putc(32); scr_putc(8);
605 if ((a == 23) && (string[0] != 0)) {
607 string[strlen(string) - 1] = 0;
608 scr_putc(8); scr_putc(32); scr_putc(8);
609 } while (!IsEmptyStr(string) && string[strlen(string) - 1] != ' ');
631 * strprompt() - prompt for a string, print the existing value and
632 * allow the user to press return to keep it...
634 void strprompt(char *prompt, char *str, int len)
641 scr_printf("%s ", prompt);
644 color(BRIGHT_MAGENTA);
647 scr_printf("%s", str);
650 for (i=0; !IsEmptyStr(&str[i]); ++i) {
660 ctdl_getline(buf, len);
667 * boolprompt() - prompt for a yes/no, print the existing value and
668 * allow the user to press return to keep it...
670 int boolprompt(char *prompt, int prev_val)
675 scr_printf("%s ", prompt);
678 color(BRIGHT_MAGENTA);
679 scr_printf("%s", (prev_val ? "Yes" : "No"));
683 r = (yesno_d(prev_val));
689 * intprompt() - like strprompt(), except for an integer
690 * (note that it RETURNS the new value!)
692 int intprompt(char *prompt, int ival, int imin, int imax)
700 snprintf(buf, sizeof buf, "%d", i);
701 strprompt(prompt, buf, 15);
703 for (p=0; !IsEmptyStr(&buf[p]); ++p) {
704 if ( (!isdigit(buf[p]))
705 && ( (buf[p]!='-') || (p!=0) ) )
709 scr_printf("*** Must be no less than %d.\n", imin);
711 scr_printf("*** Must be no more than %d.\n", imax);
712 } while ((i < imin) || (i > imax));
717 * newprompt() - prompt for a string with no existing value
718 * (clears out string buffer first)
720 void newprompt(char *prompt, char *str, int len)
722 color(BRIGHT_MAGENTA);
723 scr_printf("%s", prompt);
725 ctdl_getline(str, len);
731 { /* returns a lower case value */
740 * parse the citadel.rc file
742 void load_command_set(void)
746 char editor_key[100];
748 struct citcmd *lastcmd = NULL;
754 /* first, set up some defaults for non-required variables */
756 for (i = 0; i < MAX_EDITORS; i++)
757 strcpy(editor_paths[i], "");
758 strcpy(printcmd, "");
759 strcpy(imagecmd, "");
760 strcpy(rc_username, "");
761 strcpy(rc_password, "");
764 rc_allow_attachments = 0;
765 rc_remember_passwords = 0;
766 strcpy(rc_exp_cmd, "");
767 rc_display_message_numbers = 0;
768 rc_force_mail_prompts = 0;
771 strcpy(rc_url_cmd, "");
772 strcpy(rc_gotmail_cmd, "");
774 rc_encrypt = RC_DEFAULT;
776 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
777 rc_screen = RC_DEFAULT;
779 rc_alt_semantics = 0;
781 /* now try to open the citadel.rc file */
784 if (getenv("HOME") != NULL) {
785 snprintf(buf, sizeof buf, "%s/.citadelrc", getenv("HOME"));
786 ccfile = fopen(buf, "r");
788 if (ccfile == NULL) {
789 ccfile = fopen(file_citadel_rc, "r");
791 if (ccfile == NULL) {
792 ccfile = fopen("/etc/citadel.rc", "r");
794 if (ccfile == NULL) {
795 ccfile = fopen("./citadel.rc", "r");
797 if (ccfile == NULL) {
798 perror("commands: cannot open citadel.rc");
801 while (fgets(buf, sizeof buf, ccfile) != NULL) {
802 while ((!IsEmptyStr(buf)) ? (isspace(buf[strlen(buf) - 1])) : 0)
803 buf[strlen(buf) - 1] = 0;
805 if (!strncasecmp(buf, "encrypt=", 8)) {
806 if (!strcasecmp(&buf[8], "yes")) {
810 fprintf(stderr, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
815 else if (!strcasecmp(&buf[8], "no")) {
818 else if (!strcasecmp(&buf[8], "default")) {
819 rc_encrypt = RC_DEFAULT;
824 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
825 if (!strncasecmp(buf, "fullscreen=", 11)) {
826 if (!strcasecmp(&buf[11], "yes"))
828 else if (!strcasecmp(&buf[11], "no"))
833 if (!strncasecmp(buf, "editor=", 7))
834 strcpy(editor_paths[0], &buf[7]);
836 for (i = 0; i < MAX_EDITORS; i++)
838 sprintf(editor_key, "editor%d=", i);
839 if (!strncasecmp(buf, editor_key, strlen(editor_key)))
840 strcpy(editor_paths[i], &buf[strlen(editor_key)]);
843 if (!strncasecmp(buf, "printcmd=", 9))
844 strcpy(printcmd, &buf[9]);
846 if (!strncasecmp(buf, "imagecmd=", 9))
847 strcpy(imagecmd, &buf[9]);
849 if (!strncasecmp(buf, "expcmd=", 7))
850 strcpy(rc_exp_cmd, &buf[7]);
852 if (!strncasecmp(buf, "local_screen_dimensions=", 24))
853 have_xterm = (char) atoi(&buf[24]);
855 if (!strncasecmp(buf, "use_floors=", 11)) {
856 if (!strcasecmp(&buf[11], "yes"))
857 rc_floor_mode = RC_YES;
858 if (!strcasecmp(&buf[11], "no"))
859 rc_floor_mode = RC_NO;
860 if (!strcasecmp(&buf[11], "default"))
861 rc_floor_mode = RC_DEFAULT;
863 if (!strncasecmp(buf, "beep=", 5)) {
864 rc_exp_beep = atoi(&buf[5]);
866 if (!strncasecmp(buf, "allow_attachments=", 18)) {
867 rc_allow_attachments = atoi(&buf[18]);
869 if (!strncasecmp(buf, "idle_threshold=", 15)) {
870 rc_idle_threshold = atol(&buf[15]);
872 if (!strncasecmp(buf, "remember_passwords=", 19)) {
873 rc_remember_passwords = atoi(&buf[19]);
875 if (!strncasecmp(buf, "display_message_numbers=", 24)) {
876 rc_display_message_numbers = atoi(&buf[24]);
878 if (!strncasecmp(buf, "force_mail_prompts=", 19)) {
879 rc_force_mail_prompts = atoi(&buf[19]);
881 if (!strncasecmp(buf, "ansi_color=", 11)) {
882 if (!strncasecmp(&buf[11], "on", 2))
884 if (!strncasecmp(&buf[11], "auto", 4))
885 rc_ansi_color = 2; /* autodetect */
886 if (!strncasecmp(&buf[11], "user", 4))
887 rc_ansi_color = 3; /* user config */
889 if (!strncasecmp(buf, "use_background=", 15)) {
890 if (!strncasecmp(&buf[15], "on", 2))
893 if (!strncasecmp(buf, "prompt_control=", 15)) {
894 if (!strncasecmp(&buf[15], "on", 2))
895 rc_prompt_control = 1;
896 if (!strncasecmp(&buf[15], "user", 4))
897 rc_prompt_control = 3; /* user config */
899 if (!strncasecmp(buf, "username=", 9))
900 strcpy(rc_username, &buf[9]);
902 if (!strncasecmp(buf, "password=", 9))
903 strcpy(rc_password, &buf[9]);
905 if (!strncasecmp(buf, "urlcmd=", 7))
906 strcpy(rc_url_cmd, &buf[7]);
908 if (!strncasecmp(buf, "gotmailcmd=", 11))
909 strcpy(rc_gotmail_cmd, &buf[11]);
911 if (!strncasecmp(buf, "alternate_semantics=", 20)) {
912 if (!strncasecmp(&buf[20], "yes", 3)) {
913 rc_alt_semantics = 1;
916 rc_alt_semantics = 0;
920 if (!strncasecmp(buf, "cmd=", 4)) {
921 strcpy(buf, &buf[4]);
923 cptr = (struct citcmd *) malloc(sizeof(struct citcmd));
925 cptr->c_cmdnum = atoi(buf);
926 for (d = strlen(buf); d >= 0; --d)
929 strcpy(buf, &buf[b + 1]);
931 cptr->c_axlevel = atoi(buf);
932 for (d = strlen(buf); d >= 0; --d)
935 strcpy(buf, &buf[b + 1]);
937 for (a = 0; a < 5; ++a)
938 cptr->c_keys[a][0] = 0;
942 buf[strlen(buf) + 1] = 0;
943 while (!IsEmptyStr(buf)) {
945 for (d = strlen(buf); d >= 0; --d)
948 strncpy(cptr->c_keys[a], buf, b);
949 cptr->c_keys[a][b] = 0;
951 strcpy(buf, &buf[b + 1]);
961 lastcmd->next = cptr;
971 * return the key associated with a command
973 char keycmd(char *cmdstr)
977 for (a = 0; !IsEmptyStr(&cmdstr[a]); ++a)
978 if (cmdstr[a] == '&')
979 return (tolower(cmdstr[a + 1]));
985 * Output the string from a key command without the ampersand
986 * "mode" should be set to 0 for normal or 1 for <C>ommand key highlighting
988 char *cmd_expand(char *strbuf, int mode)
996 for (a = 0; exp[a]; ++a) {
997 if (strbuf[a] == '&') {
999 /* dont echo these non mnemonic command keys */
1000 int noecho = strbuf[a+1] == '<' || strbuf[a+1] == '>' || strbuf[a+1] == '+' || strbuf[a+1] == '-';
1003 strcpy(&exp[a], &exp[a + 1 + noecho]);
1007 strcpy(buf, &exp[a + 2]);
1013 if (!strncmp(&exp[a], "^r", 2)) {
1015 strcpy(&exp[a], room_name);
1016 strcat(exp, &buf[a + 2]);
1018 if (!strncmp(&exp[a], "^c", 2)) {
1020 strcpy(&exp[a + 1], &exp[a + 2]);
1030 * Comparison function to determine if entered commands match a
1031 * command loaded from the config file.
1033 int cmdmatch(char *cmdbuf, struct citcmd *cptr, int ncomp)
1044 for (a = 0; a < ncomp; ++a) {
1045 if ((tolower(cmdbuf[a]) != keycmd(cptr->c_keys[a]))
1046 || (cptr->c_axlevel > cmdax))
1054 * This function returns 1 if a given command requires a string input
1056 int requires_string(struct citcmd *cptr, int ncomp)
1061 strcpy(buf, cptr->c_keys[ncomp - 1]);
1062 for (a = 0; !IsEmptyStr(&buf[a]); ++a) {
1071 * Input a command at the main prompt.
1072 * This function returns an integer command number. If the command prompts
1073 * for a string then it is placed in the supplied buffer.
1075 int getcmd(CtdlIPC *ipc, char *argbuf)
1084 struct citcmd *cptr;
1087 * Starting a new command now, so set sigcaught to 0. This variable
1088 * is set to nonzero (usually NEXT_KEY or STOP_KEY) if a command has
1089 * been interrupted by a keypress.
1093 /* Switch color support on or off if we're in user mode */
1094 if (rc_ansi_color == 3) {
1095 if (userflags & US_COLOR)
1100 /* if we're running in idiot mode, display a cute little menu */
1101 IFNEXPERT formout(ipc, "mainmenu");
1106 for (a = 0; a < 5; ++a)
1108 /* now the room prompt... */
1109 ok_to_interrupt = 1;
1110 color(BRIGHT_WHITE);
1111 scr_printf("\n%s", room_name);
1113 scr_printf("%c ", room_prompt(room_flags));
1118 ok_to_interrupt = 0;
1120 /* Handle the backspace key, but only if there's something
1121 * to backspace over...
1123 if ((ch == 8) && (cmdpos > 0)) {
1124 back(cmdspaces[cmdpos - 1] + 1);
1128 /* Spacebar invokes "lazy traversal" commands */
1129 if ((ch == 32) && (cmdpos == 0)) {
1130 this_lazy_cmd = next_lazy_cmd;
1131 if (this_lazy_cmd == 13)
1133 if (this_lazy_cmd == 5)
1135 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1136 if (cptr->c_cmdnum == this_lazy_cmd) {
1137 for (a = 0; a < 5; ++a)
1138 if (cptr->c_keys[a][0] != 0)
1139 scr_printf("%s ", cmd_expand(
1140 cptr->c_keys[a], 0));
1142 return (this_lazy_cmd);
1146 return (this_lazy_cmd);
1148 /* Otherwise, process the command */
1149 cmdbuf[cmdpos] = tolower(ch);
1151 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1152 if (cmdmatch(cmdbuf, cptr, cmdpos + 1)) {
1154 scr_printf("%s", cmd_expand(cptr->c_keys[cmdpos], 0));
1155 cmdspaces[cmdpos] = strlen(
1156 cmd_expand(cptr->c_keys[cmdpos], 0));
1158 if ((cptr->c_keys[cmdpos + 1]) != 0)
1164 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1165 if (cmdmatch(cmdbuf, cptr, 5)) {
1166 /* We've found our command. */
1167 if (requires_string(cptr, cmdpos)) {
1168 ctdl_getline(argbuf, 64);
1173 /* If this command is one that changes rooms,
1174 * then the next lazy-command (space bar)
1175 * should be "read new" instead of "goto"
1177 if ((cptr->c_cmdnum == 5)
1178 || (cptr->c_cmdnum == 6)
1179 || (cptr->c_cmdnum == 47)
1180 || (cptr->c_cmdnum == 52)
1181 || (cptr->c_cmdnum == 16)
1182 || (cptr->c_cmdnum == 20))
1185 /* If this command is "read new"
1186 * then the next lazy-command (space bar)
1189 if (cptr->c_cmdnum == 13)
1192 return (cptr->c_cmdnum);
1198 pprintf("\rOne of ... \n");
1199 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1200 if (cmdmatch(cmdbuf, cptr, cmdpos)) {
1201 for (a = 0; a < 5; ++a) {
1202 keyopt(cmd_expand(cptr->c_keys[a], 1));
1210 pprintf("\n%s%c ", room_name, room_prompt(room_flags));
1212 for (cptr = cmdlist; cptr != NULL; cptr = cptr->next) {
1213 if ((got == 0) && (cmdmatch(cmdbuf, cptr, cmdpos))) {
1214 for (a = 0; a < cmdpos; ++a) {
1216 cmd_expand(cptr->c_keys[a], 0));
1231 * set tty modes. commands are:
1233 * 01- set to Citadel mode
1234 * 2 - save current settings for later restoral
1235 * 3 - restore saved settings
1237 #ifdef HAVE_TERMIOS_H
1238 void stty_ctdl(int cmd)
1239 { /* SysV version of stty_ctdl() */
1240 struct termios live;
1241 static struct termios saved_settings;
1242 static int last_cmd = 0;
1249 if ((cmd == 0) || (cmd == 1)) {
1250 tcgetattr(0, &live);
1251 live.c_iflag = ISTRIP | IXON | IXANY;
1252 live.c_oflag = OPOST | ONLCR;
1253 live.c_lflag = ISIG | NOFLSH;
1255 live.c_cc[VINTR] = 0;
1256 live.c_cc[VQUIT] = 0;
1259 live.c_cc[VMIN] = 0;
1260 live.c_cc[VTIME] = 0;
1263 /* do we even need this stuff anymore? */
1264 /* live.c_line=0; */
1265 live.c_cc[VERASE] = 8;
1266 live.c_cc[VKILL] = 24;
1267 live.c_cc[VEOF] = 1;
1268 live.c_cc[VEOL] = 255;
1269 live.c_cc[VEOL2] = 0;
1270 live.c_cc[VSTART] = 0;
1271 tcsetattr(0, TCSADRAIN, &live);
1274 tcgetattr(0, &saved_settings);
1277 tcsetattr(0, TCSADRAIN, &saved_settings);
1282 void stty_ctdl(int cmd)
1283 { /* BSD version of stty_ctdl() */
1285 static struct sgttyb saved_settings;
1286 static int last_cmd = 0;
1293 if ((cmd == 0) || (cmd == 1)) {
1295 live.sg_flags |= CBREAK;
1296 live.sg_flags |= CRMOD;
1297 live.sg_flags |= NL1;
1298 live.sg_flags &= ~ECHO;
1300 live.sg_flags |= NOFLSH;
1304 gtty(0, &saved_settings);
1307 stty(0, &saved_settings);
1314 * display_help() - help file viewer
1316 void display_help(CtdlIPC *ipc, char *name)
1323 * fmout() - Citadel text formatter and paginator
1326 int width, /* screen width to use */
1327 FILE *fpin, /* file to read from, or NULL to format given text */
1328 char *text, /* text to be formatted (when fpin is NULL */
1329 FILE *fpout, /* file to write to, or NULL to write to screen */
1330 char pagin, /* nonzero if we should use the paginator */
1331 int height, /* screen height to use */
1332 int starting_lp,/* starting value for lines_printed, -1 for global */
1333 int subst) /* nonzero if we should use hypertext mode */
1335 char *buffer = NULL; /* The current message */
1336 char *word = NULL; /* What we are about to actually print */
1337 char *e; /* Pointer to position in text */
1338 char old = 0; /* The previous character */
1339 int column = 0; /* Current column */
1340 size_t i; /* Generic counter */
1342 /* Space for a single word, which can be at most screenwidth */
1343 word = (char *)calloc(1, width);
1345 err_printf("Can't alloc memory to print message: %s!\n",
1350 /* Read the entire message body into memory */
1352 buffer = load_message_from_file(fpin);
1354 err_printf("Can't print message: %s!\n",
1363 if (starting_lp >= 0)
1364 lines_printed = starting_lp;
1366 /* Run the message body */
1368 /* Catch characters that shouldn't be there at all */
1373 /* First, are we looking at a newline? */
1376 if (*e == ' ') { /* Paragraph */
1378 fprintf(fpout, "\n");
1382 lines_printed = checkpagin(lines_printed, pagin, height);
1385 } else if (old != ' ') {/* Don't print two spaces */
1387 fprintf(fpout, " ");
1397 /* Are we looking at a nonprintable?
1398 * (This section is now commented out because we could be displaying
1399 * a character set like UTF-8 or ISO-8859-1.)
1400 if ( (*e < 32) || (*e > 126) ) {
1405 /* Or are we looking at a space? */
1408 if (column >= width - 1) {
1409 /* Are we in the rightmost column? */
1411 fprintf(fpout, "\n");
1415 lines_printed = checkpagin(lines_printed, pagin, height);
1418 } else if (!(column == 0 && old == ' ')) {
1419 /* Eat only the first space on a line */
1421 fprintf(fpout, " ");
1427 /* ONLY eat the FIRST space on a line */
1433 /* Read a word, slightly messy */
1436 if (!isprint(e[i]) && !isspace(e[i]))
1443 /* We should never see these, but... slightly messy */
1444 if (e[i] == '\t' || e[i] == '\f' || e[i] == '\v')
1447 /* Break up really long words */
1448 /* TODO: auto-hyphenation someday? */
1451 strncpy(word, e, i);
1454 /* Decide where to print the word */
1455 if (column + i >= width) {
1456 /* Wrap to the next line */
1458 fprintf(fpout, "\n");
1462 lines_printed = checkpagin(lines_printed, pagin, height);
1467 /* Print the word */
1469 fprintf(fpout, "%s", word);
1471 scr_printf("%s", word);
1474 e += i; /* Start over with the whitepsace! */
1478 if (fpin) /* We allocated this, remember? */
1481 /* Is this necessary? It makes the output kind of spacey. */
1483 fprintf(fpout, "\n");
1487 lines_printed = checkpagin(lines_printed, pagin, height);
1495 * support ANSI color if defined
1497 void color(int colornum)
1499 static int hold_color;
1500 static int current_color;
1502 if (colornum == COLOR_PUSH) {
1503 hold_color = current_color;
1507 if (colornum == COLOR_POP) {
1512 current_color = colornum;
1514 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1515 if (scr_color(colornum))
1518 /* When switching to dim white, actually output an 'original
1519 * pair' sequence -- this looks better on black-on-white
1520 * terminals. - Changed to ORIGINAL_PAIR as this actually
1521 * wound up looking horrible on black-on-white terminals, not
1522 * to mention transparent terminals.
1524 if (colornum == ORIGINAL_PAIR)
1525 printf("\033[0;39;49m");
1527 printf("\033[%d;3%d;4%dm",
1528 (colornum & 8) ? 1 : 0,
1536 void cls(int colornum)
1539 printf("\033[4%dm\033[2J\033[H\033[0m",
1540 colornum ? colornum : rc_color_use_bg);
1547 * Detect whether ANSI color is available (answerback)
1549 void send_ansi_detect(void)
1551 if (rc_ansi_color == 2) {
1558 void look_for_ansi(void)
1566 if (rc_ansi_color == 0) {
1568 } else if (rc_ansi_color == 1) {
1570 } else if (rc_ansi_color == 2) {
1572 /* otherwise, do the auto-detect */
1577 if ((now - AnsiDetect) < 2)
1586 select(1, &rfds, NULL, NULL, &tv);
1587 if (FD_ISSET(0, &rfds)) {
1588 abuf[strlen(abuf) + 1] = 0;
1589 read(0, &abuf[strlen(abuf)], 1);
1591 } while (FD_ISSET(0, &rfds));
1593 for (a = 0; !IsEmptyStr(&abuf[a]); ++a) {
1594 if ((abuf[a] == 27) && (abuf[a + 1] == '[')
1595 && (abuf[a + 2] == '?')) {
1604 * Display key options (highlight hotkeys inside angle brackets)
1606 void keyopt(char *buf) {
1610 for (i=0; !IsEmptyStr(&buf[i]); ++i) {
1612 pprintf("%c", buf[i]);
1613 color(BRIGHT_MAGENTA);
1615 if (buf[i]=='>'&& buf[i+1] != '>') {
1618 pprintf("%c", buf[i]);
1627 * Present a key-menu line choice type of thing
1629 char keymenu(char *menuprompt, char *menustring) {
1635 int display_prompt = 1;
1637 choices = num_tokens(menustring, '|');
1639 if (menuprompt != NULL) do_prompt = 1;
1640 if (menuprompt != NULL) if (IsEmptyStr(menuprompt)) do_prompt = 0;
1643 if (display_prompt) {
1645 scr_printf("%s ", menuprompt);
1648 for (i=0; i<choices; ++i) {
1649 extract_token(buf, menustring, i, '|', sizeof buf);
1659 if ( (do_prompt) && (ch=='?') ) {
1660 scr_printf("\rOne of... ");
1662 for (i=0; i<choices; ++i) {
1663 extract_token(buf, menustring, i, '|', sizeof buf);
1672 for (i=0; i<choices; ++i) {
1673 extract_token(buf, menustring, i, '|', sizeof buf);
1674 for (c=1; !IsEmptyStr(&buf[c]); ++c) {
1675 if ( (ch == tolower(buf[c]))
1677 && (buf[c+1]=='>') ) {
1678 for (a=0; !IsEmptyStr(&buf[a]); ++a) {
1679 if ( (a!=(c-1)) && (a!=(c+1))) {