diff --git a/Make.mk b/Make.mk index b535e9db..c1a6aa92 100644 --- a/Make.mk +++ b/Make.mk @@ -432,5 +432,5 @@ $(srcdir)/src/client/config.h.in: src/client/configure.ac src/client/aclocal.m4 cd $(dir $@) && autoheader touch $@ -$(srcdir)/src/client/aclocal.m4: m4/ax_lib_socket_nsl.m4 m4/my_terminfo.m4 m4/my_windows_api.m4 +$(srcdir)/src/client/aclocal.m4: m4/ax_lib_readline.m4 m4/ax_lib_socket_nsl.m4 m4/my_terminfo.m4 m4/my_windows_api.m4 cat $^ >$@ diff --git a/configure.ac b/configure.ac index 36d358ec..589b06ae 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,7 @@ LIBS_util="$LIBS" LIBS="$LIBS_SOCKETS $LIBS" AX_LIB_SOCKET_NSL LIBS_server="$LIBS" +AX_LIB_READLINE ### Checks for header files diff --git a/m4/ax_lib_readline.m4 b/m4/ax_lib_readline.m4 new file mode 100644 index 00000000..056f25c2 --- /dev/null +++ b/m4/ax_lib_readline.m4 @@ -0,0 +1,107 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_lib_readline.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_LIB_READLINE +# +# DESCRIPTION +# +# Searches for a readline compatible library. If found, defines +# `HAVE_LIBREADLINE'. If the found library has the `add_history' function, +# sets also `HAVE_READLINE_HISTORY'. Also checks for the locations of the +# necessary include files and sets `HAVE_READLINE_H' or +# `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or +# 'HAVE_HISTORY_H' if the corresponding include files exists. +# +# The libraries that may be readline compatible are `libedit', +# `libeditline' and `libreadline'. Sometimes we need to link a termcap +# library for readline to work, this macro tests these cases too by trying +# to link with `libtermcap', `libcurses' or `libncurses' before giving up. +# +# Here is an example of how to use the information provided by this macro +# to perform the necessary includes or declarations in a C file: +# +# #ifdef HAVE_LIBREADLINE +# # if defined(HAVE_READLINE_READLINE_H) +# # include +# # elif defined(HAVE_READLINE_H) +# # include +# # else /* !defined(HAVE_READLINE_H) */ +# extern char *readline (); +# # endif /* !defined(HAVE_READLINE_H) */ +# char *cmdline = NULL; +# #else /* !defined(HAVE_READLINE_READLINE_H) */ +# /* no readline */ +# #endif /* HAVE_LIBREADLINE */ +# +# #ifdef HAVE_READLINE_HISTORY +# # if defined(HAVE_READLINE_HISTORY_H) +# # include +# # elif defined(HAVE_HISTORY_H) +# # include +# # else /* !defined(HAVE_HISTORY_H) */ +# extern void add_history (); +# extern int write_history (); +# extern int read_history (); +# # endif /* defined(HAVE_READLINE_HISTORY_H) */ +# /* no history */ +# #endif /* HAVE_READLINE_HISTORY */ +# +# LICENSE +# +# Copyright (c) 2008 Ville Laurikari +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AU_ALIAS([VL_LIB_READLINE], [AX_LIB_READLINE]) +AC_DEFUN([AX_LIB_READLINE], [ + AC_CACHE_CHECK([for a readline compatible library], + ax_cv_lib_readline, [ + ORIG_LIBS="$LIBS" + for readline_lib in readline edit editline; do + for termcap_lib in "" termcap curses ncurses; do + if test -z "$termcap_lib"; then + TRY_LIB="-l$readline_lib" + else + TRY_LIB="-l$readline_lib -l$termcap_lib" + fi + LIBS="$ORIG_LIBS $TRY_LIB" + AC_TRY_LINK_FUNC(readline, ax_cv_lib_readline="$TRY_LIB") + if test -n "$ax_cv_lib_readline"; then + break + fi + done + if test -n "$ax_cv_lib_readline"; then + break + fi + done + if test -z "$ax_cv_lib_readline"; then + ax_cv_lib_readline="no" + fi + LIBS="$ORIG_LIBS" + ]) + + if test "$ax_cv_lib_readline" != "no"; then + LIBS="$LIBS $ax_cv_lib_readline" + AC_DEFINE(HAVE_LIBREADLINE, 1, + [Define if you have a readline compatible library]) + AC_CHECK_HEADERS(readline.h readline/readline.h) + AC_CACHE_CHECK([whether readline supports history], + ax_cv_lib_readline_history, [ + ax_cv_lib_readline_history="no" + AC_TRY_LINK_FUNC(add_history, ax_cv_lib_readline_history="yes") + ]) + if test "$ax_cv_lib_readline_history" = "yes"; then + AC_DEFINE(HAVE_READLINE_HISTORY, 1, + [Define if your readline library has \`add_history']) + AC_CHECK_HEADERS(history.h readline/history.h) + fi + fi +])dnl diff --git a/man/empire.6 b/man/empire.6 index 4be59941..66460faa 100644 --- a/man/empire.6 +++ b/man/empire.6 @@ -39,6 +39,11 @@ Use UTF-8 rather than ASCII character set. This requires server version 4.2.21 or later, and a terminal that understands UTF-8. .TP +.B \-H +Save readline command history to file. +.IP +Only available when compiled with readline and history support. +.TP .B \-v Print version information and exit. .TP diff --git a/src/client/configure.ac b/src/client/configure.ac index 0854c596..ddd49fc4 100644 --- a/src/client/configure.ac +++ b/src/client/configure.ac @@ -58,6 +58,7 @@ if test "$Windows_API" = yes; then AC_LIBOBJ([w32/w32io]) AC_LIBOBJ([w32/w32sockets]) fi +AX_LIB_READLINE ### Checks for header files. diff --git a/src/client/main.c b/src/client/main.c index 74ab4271..bfd5a0e2 100644 --- a/src/client/main.c +++ b/src/client/main.c @@ -40,6 +40,7 @@ #include #ifdef _WIN32 #include +#include #include "sys/socket.h" #else #include @@ -55,6 +56,7 @@ struct passwd { char *pw_name; + char *pw_dir; }; static struct passwd *w32_getpw(void); @@ -72,6 +74,9 @@ print_usage(char *program_name) " -r Restricted mode, no redirections\n" " -s [HOST:]PORT Specify server HOST and PORT\n" " -u Use UTF-8\n" +#ifdef HAVE_READLINE_HISTORY + " -H Save readline command history to file\n" +#endif /* HAVE_READLINE_HISTORY */ " -h display this help and exit\n" " -v display version information and exit\n", program_name); @@ -82,6 +87,7 @@ main(int argc, char **argv) { int opt; char *auxfname = NULL; + int use_history_file = 0; int send_kill = 0; char *host = NULL; char *port = NULL; @@ -90,14 +96,20 @@ main(int argc, char **argv) char *country; char *passwd; char *uname; + char *udir; char *colon; int sock; - while ((opt = getopt(argc, argv, "2:krs:uhv")) != EOF) { + while ((opt = getopt(argc, argv, "2:krs:uHhv")) != EOF) { switch (opt) { case '2': auxfname = optarg; break; +#ifdef HAVE_READLINE_HISTORY + case 'H': + use_history_file = 1; + break; +#endif /* HAVE_READLINE_HISTORY */ case 'k': send_kill = 1; break; @@ -146,7 +158,8 @@ main(int argc, char **argv) if (!host) host = empirehost; uname = getenv("LOGNAME"); - if (uname == NULL) { + udir = getenv("HOME"); + if (!uname || !udir) { struct passwd *pwd; pwd = getpwuid(getuid()); @@ -154,7 +167,10 @@ main(int argc, char **argv) fprintf(stderr, "You don't exist. Go away\n"); exit(1); } - uname = pwd->pw_name; + if (!uname) + uname = pwd->pw_name; + if (!udir) + udir = pwd->pw_dir; } if (*ap) { fprintf(stderr, "%s: extra operand %s\n", argv[0], *ap); @@ -172,6 +188,12 @@ main(int argc, char **argv) sock = tcp_connect(host, port); + if (use_history_file) { + history_file = malloc(1024); + strncpy(history_file, udir, 1000); + strcat(history_file, "/.empire.history"); + } + if (!login(sock, uname, country, passwd, send_kill, utf8)) exit(1); @@ -183,12 +205,13 @@ main(int argc, char **argv) #ifdef _WIN32 /* - * Get Windows user name + * Get Windows user name and directory */ static struct passwd * w32_getpw(void) { static char unamebuf[128]; + static char udirbuf[MAX_PATH]; static struct passwd pwd; DWORD unamesize; @@ -199,6 +222,9 @@ w32_getpw(void) pwd.pw_name = "nobody"; } else pwd.pw_name = "nobody"; + if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_PROFILE, NULL, 0, udirbuf)) + && strlen(udirbuf) == 0) + pwd.pw_dir = udirbuf; return &pwd; } diff --git a/src/client/misc.h b/src/client/misc.h index 29a548a5..0a09be76 100644 --- a/src/client/misc.h +++ b/src/client/misc.h @@ -44,6 +44,7 @@ extern char empireport[]; extern int eight_bit_clean; extern FILE *auxfp; extern int restricted; +extern char *history_file; #ifdef HAVE_CURSES_TERMINFO void getsose(void); diff --git a/src/client/play.c b/src/client/play.c index 210ff733..ad7028c7 100644 --- a/src/client/play.c +++ b/src/client/play.c @@ -51,6 +51,28 @@ #include "ringbuf.h" #include "secure.h" +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* !defined(HAVE_READLINE_H) */ +#endif /* HAVE_LIBREADLINE */ + +#ifdef HAVE_READLINE_HISTORY +# if defined(HAVE_READLINE_HISTORY_H) +# include +# elif defined(HAVE_HISTORY_H) +# include +# else /* !defined(HAVE_HISTORY_H) */ +extern void add_history (); +extern int write_history (); +extern int read_history (); +# endif /* !defined(HAVE_HISTORY_H) */ +#endif /* HAVE_READLINE_HISTORY */ + #define EOF_COOKIE "ctld\n" #define INTR_COOKIE "aborted\n" @@ -414,6 +436,24 @@ recv_output(int sock) return n; } +char *history_file = NULL; +#ifdef HAVE_LIBREADLINE +static char *input_from_rl; +static int has_rl_input; + +static void +input_handler(char *line) +{ + input_from_rl = line; + has_rl_input = 1; + rl_already_prompted = 1; +#ifdef HAVE_READLINE_HISTORY + if (line && *line) + add_history(line); +#endif /* HAVE_READLINE_HISTORY */ +} +#endif /* HAVE_LIBREADLINE */ + /* * Receive player input from @fd into @inbuf. * Return 1 on receipt of input, zero on EOF, -1 on error. @@ -423,8 +463,33 @@ recv_input(int fd, struct ring *inbuf) { int n; int res = 1; +#ifdef HAVE_LIBREADLINE + size_t len; - n = ring_from_file(inbuf, fd); + if (fd == 0) { + if (!has_rl_input) + rl_callback_read_char(); + if (!has_rl_input) + return 1; + if (input_from_rl) { + len = strlen(input_from_rl); + n = ring_space(inbuf); + assert(n); + if (len >= (size_t)n) { + ring_putm(inbuf, input_from_rl, n); + memmove(input_from_rl, input_from_rl + n, len - n + 1); + } else { + ring_putm(inbuf, input_from_rl, len); + ring_putc(inbuf, '\n'); + free(input_from_rl); + has_rl_input = 0; + n = len + 1; + } + } else + n = 0; + } else +#endif + n = ring_from_file(inbuf, fd); if (n < 0) return -1; if (n == 0) { @@ -490,6 +555,7 @@ play(int sock) int send_eof; /* need to send EOF_COOKIE */ fd_set rdfd, wrfd; int n; + int ret = -1; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); @@ -497,6 +563,14 @@ play(int sock) sigaction(SIGINT, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, NULL); +#ifdef HAVE_LIBREADLINE +#ifdef HAVE_READLINE_HISTORY + if (history_file) + read_history(history_file); +#endif /* HAVE_READLINE_HISTORY */ + rl_bind_key('\t', rl_insert); /* Disable tab completion */ + rl_callback_handler_install("", input_handler); +#endif /* HAVE_LIBREADLINE */ ring_init(&inbuf); eof_fd0 = partial_line_sent = send_eof = send_intr = 0; @@ -525,7 +599,7 @@ play(int sock) if (n < 0) { if (errno != EINTR) { perror("select"); - return -1; + break; } } @@ -544,7 +618,6 @@ play(int sock) input_fd = 0; } } - if (n < 0) continue; @@ -571,7 +644,7 @@ play(int sock) sigaction(SIGINT, &sa, NULL); send_intr = 0; } - } else + } else if (ring_len(&inbuf) > 0) partial_line_sent = ring_peek(&inbuf, -1) != '\n'; } @@ -580,7 +653,7 @@ play(int sock) n = send_input(sock, &inbuf); if (n < 0) { perror("write socket"); - return -1; + break; } } @@ -589,10 +662,22 @@ play(int sock) n = recv_output(sock); if (n < 0) { perror("read socket"); - return -1; + break; + } + if (n == 0) { +#ifdef HAVE_LIBREADLINE +#ifdef HAVE_READLINE_HISTORY + if (history_file) + write_history(history_file); +#endif /* HAVE_READLINE_HISTORY */ +#endif /* HAVE_LIBREADLINE */ + ret = 0; + break; } - if (n == 0) - return 0; } } +#ifdef HAVE_LIBREADLINE + rl_callback_handler_remove(); +#endif + return ret; } diff --git a/src/client/servcmd.c b/src/client/servcmd.c index c02390dc..a8cce6fd 100644 --- a/src/client/servcmd.c +++ b/src/client/servcmd.c @@ -46,6 +46,14 @@ #include "proto.h" #include "secure.h" +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) +# include +# endif /* defined(HAVE_READLINE_H) */ +#endif /* HAVE_LIBREADLINE */ + int eight_bit_clean; FILE *auxfp; int restricted; @@ -131,10 +139,18 @@ static void prompt(int code, char *prompt, char *teles) { char *nl; + char pr[1024]; nl = code == C_PROMPT || code == C_INFORM ? "\n" : ""; - printf("%s%s%s", nl, teles, prompt); + snprintf(pr, sizeof(pr), "%s%s", teles, prompt); +#ifdef HAVE_LIBREADLINE + rl_set_prompt(pr); + printf("%s", nl); + rl_forced_update_display(); +#else /* !HAVE_LIBREADLINE */ + printf("%s%s", nl, pr); fflush(stdout); +#endif /* !HAVE_LIBREADLINE */ if (auxfp) { fprintf(auxfp, "%s%s%s", nl, teles, prompt); fflush(auxfp);