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
46 #include <libcitadel.h>
48 #include "citadel_ipc.h"
51 #include "citadel_decls.h"
53 #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_open_cmd
[SIZ
];
85 char rc_gotmail_cmd
[SIZ
];
88 int next_lazy_cmd
= 5;
90 int lines_printed
= 0; /* line count for paginator */
91 extern int screenwidth
, screenheight
;
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) {
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
);
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
)
151 if (sigcaught
) return(lp
);
152 thekey
= was_a_key_pressed();
153 if (thekey
== 'q' || thekey
== 'Q' || thekey
== 's' || thekey
== 'S')
155 if (thekey
== 'n' || thekey
== 'N')
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
);
174 * pprintf() ... paginated version of printf()
176 void pprintf(const char *format
, ...) {
178 static char buf
[4096]; /* static for performance, change if needed */
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
);
191 for (i
=0; !IsEmptyStr(&buf
[i
]); ++i
) {
195 lines_printed
= checkpagin(lines_printed
,
196 (userflags
& US_PAGINATOR
),
205 * print_instant() - print instant messages if there are any
207 void print_instant(void)
216 char *listing
= NULL
;
217 int r
; /* IPC result code */
219 if (instant_msgs
== 0)
225 if (IsEmptyStr(rc_exp_cmd
)) {
230 while (instant_msgs
!= 0) {
231 r
= CtdlIPCGetInstantMessage(ipc_for_signal_handlers
, &listing
, buf
);
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(×tamp
, &stamp
);
244 /* If the page is a Logoff Request, honor it. */
250 if (!IsEmptyStr(rc_exp_cmd
)) {
251 outpipe
= popen(rc_exp_cmd
, "w");
252 if (outpipe
!= NULL
) {
253 /* Header derived from flags */
256 "Please log off now, as requested ");
258 fprintf(outpipe
, "Broadcast message ");
260 fprintf(outpipe
, "Chat request ");
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",
267 stamp
.tm_hour
? 'p' : 'a');
268 else if (stamp
.tm_hour
> 12) /* pm */
269 fprintf(outpipe
, "at %d:%02dpm",
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
);
280 if (instant_msgs
== 0)
285 /* fall back to built-in instant message display */
289 /* Header derived from flags */
291 scr_printf("Please log off now, as requested ");
293 scr_printf("Broadcast message ");
295 scr_printf("Chat request ");
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
);
307 scr_printf("at %d:%02dam", stamp
.tm_hour
, stamp
.tm_min
);
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
);
318 fmout(screenwidth
, NULL
, listing
, NULL
, 1, screenheight
, -1, 0);
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
331 if (!is_curses_enabled())
334 scr_printf("\n---\n");
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 */
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
)
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", "");
371 scr_printf("%s%c ", room_name
,
372 room_prompt(room_flags
));
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];
402 sprintf(threadName
, "ka_Thread n");
404 // Register for tracing
405 eCrash_RegisterThread(threadName
, 0);
407 really_do_keepalive();
408 pthread_detach(ka_thr_handle
);
411 #ifdef HAVE_BACKTRACE
412 eCrash_UnregisterThread();
417 /* start up a thread to handle a keepalive in the background */
418 static void async_ka_exec(void)
420 if (!ka_thr_active
) {
422 if (pthread_create(&ka_thr_handle
, NULL
, ka_thread
, NULL
)) {
423 perror("pthread_create");
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)
439 if ((now
- idlet
) < ((long) S_KEEPALIVE
))
442 /* Do a space-backspace to keep telnet sessions from idling out */
443 scr_printf(" %c", 8);
446 #ifdef THREADED_CLIENT
447 if (async_ka_enabled
)
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
465 pthread_join(ka_thr_handle
, NULL
);
471 /* tell do_keepalive() that keepalives are asynchronous. */
472 void async_ka_start(void)
474 #ifdef THREADED_CLIENT
481 { /* get a character from the keyboard, with */
482 int a
; /* the watchdog timer in effect if necessary */
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.
497 scr_set_windowsize(ipc_for_signal_handlers
);
499 scr_set_windowsize(ipc_for_signal_handlers
);
503 tv
.tv_sec
= S_KEEPALIVE
;
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
);
519 /* not so fast there dude, we have to handle UTF-8 and ISO-8859-1...
523 if (((a != 23) && (a != 4) && (a != 10) && (a != 8) && (a != NEXT_KEY) && (a != STOP_KEY))
524 && ((a < 32) || (a > 126))) {
529 #ifndef DISABLE_CURSES
530 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
543 { /* Returns 1 for yes, 0 for no */
559 /* Returns 1 for yes, 0 for no, arg is default value */
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
)
599 /* a = (a & 127); ** commented out because it isn't just an ASCII world anymore */
600 if ((a
== 8 || a
== 23) && (IsEmptyStr(string
)))
602 if ((a
!= 10) && (a
!= 8) && (strlen(string
) == lim
))
604 if ((a
== 8) && (string
[0] != 0)) {
605 string
[strlen(string
) - 1] = 0;
606 scr_putc(8); scr_putc(32); scr_putc(8);
609 if ((a
== 23) && (string
[0] != 0)) {
611 string
[strlen(string
) - 1] = 0;
612 scr_putc(8); scr_putc(32); scr_putc(8);
613 } while (!IsEmptyStr(string
) && string
[strlen(string
) - 1] != ' ');
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
)
645 scr_printf("%s ", prompt
);
648 color(BRIGHT_MAGENTA
);
651 scr_printf("%s", str
);
654 for (i
=0; !IsEmptyStr(&str
[i
]); ++i
) {
664 ctdl_getline(buf
, len
);
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
)
679 scr_printf("%s ", prompt
);
682 color(BRIGHT_MAGENTA
);
683 scr_printf("%s", (prev_val
? "Yes" : "No"));
687 r
= (yesno_d(prev_val
));
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
)
704 snprintf(buf
, sizeof buf
, "%d", i
);
705 strprompt(prompt
, buf
, 15);
707 for (p
=0; !IsEmptyStr(&buf
[p
]); ++p
) {
708 if ( (!isdigit(buf
[p
]))
709 && ( (buf
[p
]!='-') || (p
!=0) ) )
713 scr_printf("*** Must be no less than %d.\n", imin
);
715 scr_printf("*** Must be no more than %d.\n", imax
);
716 } while ((i
< imin
) || (i
> imax
));
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
);
729 ctdl_getline(str
, len
);
735 { /* returns a lower case value */
744 * parse the citadel.rc file
746 void load_command_set(void)
750 char editor_key
[100];
752 struct citcmd
*lastcmd
= NULL
;
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
, "");
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;
775 strcpy(rc_url_cmd
, "");
776 strcpy(rc_open_cmd
, "");
777 strcpy(rc_gotmail_cmd
, "");
779 rc_encrypt
= RC_DEFAULT
;
781 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
782 rc_screen
= RC_DEFAULT
;
784 rc_alt_semantics
= 0;
786 /* now try to open the citadel.rc file */
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");
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")) {
815 fprintf(stderr
, "citadel.rc requires encryption support but citadel is not compiled with OpenSSL");
820 else if (!strcasecmp(&buf
[8], "no")) {
823 else if (!strcasecmp(&buf
[8], "default")) {
824 rc_encrypt
= RC_DEFAULT
;
829 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
830 if (!strncasecmp(buf
, "fullscreen=", 11)) {
831 if (!strcasecmp(&buf
[11], "yes"))
833 else if (!strcasecmp(&buf
[11], "no"))
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))
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))
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;
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
)
937 strcpy(buf
, &buf
[b
+ 1]);
939 cptr
->c_axlevel
= atoi(buf
);
940 for (d
= strlen(buf
); d
>= 0; --d
)
943 strcpy(buf
, &buf
[b
+ 1]);
945 for (a
= 0; a
< 5; ++a
)
946 cptr
->c_keys
[a
][0] = 0;
950 buf
[strlen(buf
) + 1] = 0;
951 while (!IsEmptyStr(buf
)) {
953 for (d
= strlen(buf
); d
>= 0; --d
)
956 strncpy(cptr
->c_keys
[a
], buf
, b
);
957 cptr
->c_keys
[a
][b
] = 0;
959 strcpy(buf
, &buf
[b
+ 1]);
969 lastcmd
->next
= cptr
;
979 * return the key associated with a command
981 char keycmd(char *cmdstr
)
985 for (a
= 0; !IsEmptyStr(&cmdstr
[a
]); ++a
)
986 if (cmdstr
[a
] == '&')
987 return (tolower(cmdstr
[a
+ 1]));
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
)
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] == '-';
1011 strcpy(&exp
[a
], &exp
[a
+ 1 + noecho
]);
1015 strcpy(buf
, &exp
[a
+ 2]);
1021 if (!strncmp(&exp
[a
], "^r", 2)) {
1023 strcpy(&exp
[a
], room_name
);
1024 strcat(exp
, &buf
[a
+ 2]);
1026 if (!strncmp(&exp
[a
], "^c", 2)) {
1028 strcpy(&exp
[a
+ 1], &exp
[a
+ 2]);
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
)
1052 for (a
= 0; a
< ncomp
; ++a
) {
1053 if ((tolower(cmdbuf
[a
]) != keycmd(cptr
->c_keys
[a
]))
1054 || (cptr
->c_axlevel
> cmdax
))
1062 * This function returns 1 if a given command requires a string input
1064 int requires_string(struct citcmd
*cptr
, int ncomp
)
1069 strcpy(buf
, cptr
->c_keys
[ncomp
- 1]);
1070 for (a
= 0; !IsEmptyStr(&buf
[a
]); ++a
) {
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
)
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.
1101 /* Switch color support on or off if we're in user mode */
1102 if (rc_ansi_color
== 3) {
1103 if (userflags
& US_COLOR
)
1108 /* if we're running in idiot mode, display a cute little menu */
1109 IFNEXPERT
formout(ipc
, "mainmenu");
1114 for (a
= 0; a
< 5; ++a
)
1116 /* now the room prompt... */
1117 ok_to_interrupt
= 1;
1118 color(BRIGHT_WHITE
);
1119 scr_printf("\n%s", room_name
);
1121 scr_printf("%c ", room_prompt(room_flags
));
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);
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)
1141 if (this_lazy_cmd
== 5)
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));
1150 return (this_lazy_cmd
);
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));
1166 if ((cptr
->c_keys
[cmdpos
+ 1]) != 0)
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);
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))
1193 /* If this command is "read new"
1194 * then the next lazy-command (space bar)
1197 if (cptr
->c_cmdnum
== 13)
1200 return (cptr
->c_cmdnum
);
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));
1218 pprintf("\n%s%c ", room_name
, room_prompt(room_flags
));
1220 for (cptr
= cmdlist
; cptr
!= NULL
; cptr
= cptr
->next
) {
1221 if ((got
== 0) && (cmdmatch(cmdbuf
, cptr
, cmdpos
))) {
1222 for (a
= 0; a
< cmdpos
; ++a
) {
1224 cmd_expand(cptr
->c_keys
[a
], 0));
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;
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;
1267 live
.c_cc
[VMIN
] = 0;
1268 live
.c_cc
[VTIME
] = 0;
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
);
1282 tcgetattr(0, &saved_settings
);
1285 tcsetattr(0, TCSADRAIN
, &saved_settings
);
1290 void stty_ctdl(int cmd
)
1291 { /* BSD version of stty_ctdl() */
1293 static struct sgttyb saved_settings
;
1294 static int last_cmd
= 0;
1301 if ((cmd
== 0) || (cmd
== 1)) {
1303 live
.sg_flags
|= CBREAK
;
1304 live
.sg_flags
|= CRMOD
;
1305 live
.sg_flags
|= NL1
;
1306 live
.sg_flags
&= ~ECHO
;
1308 live
.sg_flags
|= NOFLSH
;
1312 gtty(0, &saved_settings
);
1315 stty(0, &saved_settings
);
1322 * display_help() - help file viewer
1324 void display_help(CtdlIPC
*ipc
, char *name
)
1331 * fmout() - Citadel text formatter and paginator
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
);
1353 err_printf("Can't alloc memory to print message: %s!\n",
1358 /* Read the entire message body into memory */
1360 buffer
= load_message_from_file(fpin
);
1362 err_printf("Can't print message: %s!\n",
1371 if (starting_lp
>= 0)
1372 lines_printed
= starting_lp
;
1374 /* Run the message body */
1376 /* Catch characters that shouldn't be there at all */
1381 /* First, are we looking at a newline? */
1384 if (*e
== ' ') { /* Paragraph */
1386 fprintf(fpout
, "\n");
1390 lines_printed
= checkpagin(lines_printed
, pagin
, height
);
1393 } else if (old
!= ' ') {/* Don't print two spaces */
1395 fprintf(fpout
, " ");
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) ) {
1413 /* Or are we looking at a space? */
1416 if (column
>= width
- 1) {
1417 /* Are we in the rightmost column? */
1419 fprintf(fpout
, "\n");
1423 lines_printed
= checkpagin(lines_printed
, pagin
, height
);
1426 } else if (!(column
== 0 && old
== ' ')) {
1427 /* Eat only the first space on a line */
1429 fprintf(fpout
, " ");
1435 /* ONLY eat the FIRST space on a line */
1441 /* Read a word, slightly messy */
1444 if (!isprint(e
[i
]) && !isspace(e
[i
]))
1451 /* We should never see these, but... slightly messy */
1452 if (e
[i
] == '\t' || e
[i
] == '\f' || e
[i
] == '\v')
1455 /* Break up really long words */
1456 /* TODO: auto-hyphenation someday? */
1459 strncpy(word
, e
, i
);
1462 /* Decide where to print the word */
1463 if (column
+ i
>= width
) {
1464 /* Wrap to the next line */
1466 fprintf(fpout
, "\n");
1470 lines_printed
= checkpagin(lines_printed
, pagin
, height
);
1475 /* Print the word */
1477 fprintf(fpout
, "%s", word
);
1479 scr_printf("%s", word
);
1482 e
+= i
; /* Start over with the whitepsace! */
1486 if (fpin
) /* We allocated this, remember? */
1489 /* Is this necessary? It makes the output kind of spacey. */
1491 fprintf(fpout
, "\n");
1495 lines_printed
= checkpagin(lines_printed
, pagin
, height
);
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
;
1515 if (colornum
== COLOR_POP
) {
1520 current_color
= colornum
;
1522 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
1523 if (scr_color(colornum
))
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");
1535 printf("\033[%d;3%d;4%dm",
1536 (colornum
& 8) ? 1 : 0,
1544 void cls(int colornum
)
1547 printf("\033[4%dm\033[2J\033[H\033[0m",
1548 colornum
? colornum
: rc_color_use_bg
);
1555 * Detect whether ANSI color is available (answerback)
1557 void send_ansi_detect(void)
1559 if (rc_ansi_color
== 2) {
1566 void look_for_ansi(void)
1574 if (rc_ansi_color
== 0) {
1576 } else if (rc_ansi_color
== 1) {
1578 } else if (rc_ansi_color
== 2) {
1580 /* otherwise, do the auto-detect */
1585 if ((now
- AnsiDetect
) < 2)
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] == '?')) {
1612 * Display key options (highlight hotkeys inside angle brackets)
1614 void keyopt(char *buf
) {
1618 for (i
=0; !IsEmptyStr(&buf
[i
]); ++i
) {
1620 pprintf("%c", buf
[i
]);
1621 color(BRIGHT_MAGENTA
);
1623 if (buf
[i
]=='>'&& buf
[i
+1] != '>') {
1626 pprintf("%c", buf
[i
]);
1635 * Present a key-menu line choice type of thing
1637 char keymenu(char *menuprompt
, char *menustring
) {
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;
1651 if (display_prompt
) {
1653 scr_printf("%s ", menuprompt
);
1656 for (i
=0; i
<choices
; ++i
) {
1657 extract_token(buf
, menustring
, i
, '|', sizeof buf
);
1667 if ( (do_prompt
) && (ch
=='?') ) {
1668 scr_printf("\rOne of... ");
1670 for (i
=0; i
<choices
; ++i
) {
1671 extract_token(buf
, menustring
, i
, '|', sizeof buf
);
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
]))
1685 && (buf
[c
+1]=='>') ) {
1686 for (a
=0; !IsEmptyStr(&buf
[a
]); ++a
) {
1687 if ( (a
!=(c
-1)) && (a
!=(c
+1))) {