From 96977e2f712b0a1d8e5eef29230c5835fbec0315 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Tue, 5 Feb 2013 12:56:18 -0600 Subject: [PATCH 01/10] configure.ac: Add build system test for readline Test for readline presence and configuration time options controlling readline use in rigctl and rotctl. --- configure.ac | 25 ++++++++- macros/ax_lib_readline.m4 | 107 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 macros/ax_lib_readline.m4 diff --git a/configure.ac b/configure.ac index e3efb9d44..5ed79b819 100644 --- a/configure.ac +++ b/configure.ac @@ -112,7 +112,7 @@ AC_CHECK_HEADERS([errno.h fcntl.h getopt.h limits.h locale.h malloc.h \ netdb.h sgtty.h stddef.h termio.h termios.h values.h ws2tcpip.h \ arpa/inet.h dev/ppbus/ppbconf.hdev/ppbus/ppi.h \ linux/hidraw.h linux/ioctl.h linux/parport.h linux/ppdev.h netinet/in.h \ -sys/ioccom.h sys/ioctl.h sys/param.h sys/socket.h sys/time.h]) +sys/ioccom.h sys/ioctl.h sys/param.h sys/socket.h sys/stat.h sys/time.h]) ## ------------------------------------ ## @@ -356,6 +356,28 @@ AC_SUBST([LIBXML2_LIBS]) AC_SUBST([LIBXML2_CFLAGS]) +dnl Check if readline support in rigctl/rotctl is wanted +AC_MSG_CHECKING([whether to use readline in rigctl/rotctl]) +AC_ARG_WITH([readline], + [AS_HELP_STRING([--without-readline], + [disable readline in rigctl/rotctl @<:@default=yes@:>@])], + [cf_with_readline_support=no], + [cf_with_readline_support=yes] + ) + +AC_MSG_RESULT([$cf_with_readline_support]) + +AS_IF([test x"$cf_with_readline_support" != "xno"], [ + # macros/ax_lib_readline.m4 + AX_LIB_READLINE + ]) + +AS_IF([test x"$ax_cv_lib_readline" = "xno"], [ + AC_MSG_WARN([readline support not found, using internal input handling.]) + cf_with_readline_support=no + ]) + + dnl Check if libgd-dev is installed, so we can enable rigmatrix AC_MSG_CHECKING([whether to build HTML rig feature matrix]) AC_ARG_ENABLE([html-matrix], @@ -701,6 +723,7 @@ echo \ With TCL binding ${build_tcl} With rigmem XML support ${cf_with_xml_support} With included ltdl ${with_included_ltdl} + With Readline support ${cf_with_readline_support} Enable HTML rig feature matrix ${cf_enable_html_matrix} Enable WinRadio ${cf_with_winradio} diff --git a/macros/ax_lib_readline.m4 b/macros/ax_lib_readline.m4 new file mode 100644 index 000000000..056f25c2a --- /dev/null +++ b/macros/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 From d54d737ba5d66f746cc3e83ea3beabd242327505 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Mon, 18 Feb 2013 12:23:34 -0600 Subject: [PATCH 02/10] rigctl_parse.c: Implement readline interactive mode Initial implementation of Readline input handling. Only if 'configure' finds Readline will it be enabled and when enabled it will only be used by rigctl in interactive mode. Passing rig commands from the rigctl command line and rigctld use the original input handling which has not been modified. --- tests/rigctl.c | 30 +- tests/rigctl_parse.c | 723 +++++++++++++++++++++++++++++++++---------- 2 files changed, 594 insertions(+), 159 deletions(-) diff --git a/tests/rigctl.c b/tests/rigctl.c index b39b6e194..f53904d88 100644 --- a/tests/rigctl.c +++ b/tests/rigctl.c @@ -34,9 +34,21 @@ #include #include #include - #include +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) /* !defined(HAVE_READLINE_READLINE_H) */ +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* HAVE_READLINE_H */ +#else +/* no readline */ +#endif /* HAVE_LIBREADLINE */ + + #include #include "misc.h" #include "iofunc.h" @@ -83,6 +95,14 @@ static struct option long_options[] = #define MAXCONFLEN 128 +/* variable for readline support */ +#ifdef HAVE_LIBREADLINE +static const int have_rl = 1; +#else /* no readline */ +static const int have_rl = 0; +#endif + + int interactive = 1; /* if no cmd on command line, switch to interactive */ int prompt = 1; /* Print prompt in rigctl */ int vfo_mode = 0; /* vfo_mode = 0 means target VFO is 'currVFO' */ @@ -327,6 +347,14 @@ int main (int argc, char *argv[]) exitcode = 0; +#ifdef HAVE_LIBREADLINE + + if (interactive && prompt && have_rl) { + rl_readline_name = "rigctl"; + } + +#endif /* HAVE_LIBREADLINE */ + do { retcode = rigctl_parse(my_rig, stdin, stdout, argv, argc); if (retcode == 2) diff --git a/tests/rigctl_parse.c b/tests/rigctl_parse.c index de85a1423..11bb989d7 100644 --- a/tests/rigctl_parse.c +++ b/tests/rigctl_parse.c @@ -36,7 +36,18 @@ #include #include -#include +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) /* !defined(HAVE_READLINE_READLINE_H) */ +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* HAVE_READLINE_H */ +#else +/* no readline */ +#endif /* HAVE_LIBREADLINE */ + #include #include "misc.h" @@ -73,6 +84,16 @@ static pthread_mutex_t rig_mutex = PTHREAD_MUTEX_INITIALIZER; #define ARG_IN (ARG_IN1|ARG_IN2|ARG_IN3|ARG_IN4) #define ARG_OUT (ARG_OUT1|ARG_OUT2|ARG_OUT3|ARG_OUT4) +/* variables for readline support */ +#ifdef HAVE_LIBREADLINE +static char *input_line = (char *)NULL; +static char *result = (char *)NULL; +static char *parsed_input[sizeof(char) * 5]; +static const int have_rl = 1; +#else /* no readline */ +static const int have_rl = 0; +#endif + struct test_table { unsigned char cmd; const char *name; @@ -308,6 +329,35 @@ void hash_delete_all() { } +#ifdef HAVE_LIBREADLINE +/* Frees allocated memory and sets pointers to NULL before calling readline + * and then parses the input into space separated tokens. + */ +static void rp_getline(const char *s) +{ + int i; + + /* free allocated memory and set pointers to NULL */ + if (input_line) { + free(input_line); + input_line = (char *)NULL; + } + + if (result) { + result = (char *)NULL; + } + + for (i = 0; i < 5; i++) + parsed_input[i] = NULL; + + /* Action! Returns typed line with newline stripped. */ + input_line = readline(s); +} + + +#endif + + /* * TODO: use Lex? */ @@ -348,6 +398,7 @@ static int scanfc(FILE *fin, const char *format, void *p) #define MAXARGSZ 127 + extern int interactive; extern int prompt; extern int vfo_mode; @@ -359,194 +410,548 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) { int retcode; /* generic return code from functions */ unsigned char cmd; - struct test_table *cmd_entry; + struct test_table *cmd_entry = NULL; - char arg1[MAXARGSZ+1], *p1; - char arg2[MAXARGSZ+1], *p2; - char arg3[MAXARGSZ+1], *p3; + char arg1[MAXARGSZ+1], *p1 = NULL; + char arg2[MAXARGSZ+1], *p2 = NULL; + char arg3[MAXARGSZ+1], *p3 = NULL; static int last_was_ret = 1; vfo_t vfo = RIG_VFO_CURR; - if (interactive) { - if (prompt) - fprintf_flush(fout, "\nRig command: "); + /* cmd, internal, rigctld */ + if (!(interactive && prompt && have_rl)) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "\nRig command: "); - do { - if (scanfc(fin, "%c", &cmd) < 1) - return -1; - - /* Extended response protocol requested with leading '+' on command - * string--rigctld only! - */ - if (cmd == '+' && !prompt) { - ext_resp = 1; + do { if (scanfc(fin, "%c", &cmd) < 1) return -1; - } else if (cmd == '+' && prompt) { - return 0; - } - if (cmd != '\\' && cmd != '_' && cmd != '#' && ispunct(cmd) && !prompt) { - ext_resp = 1; - resp_sep = cmd; - if (scanfc(fin, "%c", &cmd) < 1) - return -1; - } else if (cmd != '\\' && cmd != '?' && cmd != '_' && cmd != '#' && ispunct(cmd) && prompt) { - return 0; - } - - /* command by name */ - if (cmd == '\\') { - unsigned char cmd_name[MAXNAMSIZ], *pcmd = cmd_name; - int c_len = MAXNAMSIZ; - - if (scanfc(fin, "%c", pcmd) < 1) - return -1; - - while(c_len-- && (isalnum(*pcmd) || *pcmd == '_' )) - if (scanfc(fin, "%c", ++pcmd) < 1) + /* Extended response protocol requested with leading '+' on command + * string--rigctld only! + */ + if (cmd == '+' && !prompt) { + ext_resp = 1; + if (scanfc(fin, "%c", &cmd) < 1) return -1; - - *pcmd = '\0'; - cmd = parse_arg((char *)cmd_name); - break; - } - - if (cmd == 0x0a || cmd == 0x0d) { - if (last_was_ret) { - if (prompt) { - fprintf(fout, "? for help, q to quit.\n"); - fprintf_flush(fout, "\nRig command: "); - } + } else if (cmd == '+' && prompt) { return 0; } - last_was_ret = 1; + + if (cmd != '\\' && cmd != '_' && cmd != '#' && ispunct(cmd) && !prompt) { + ext_resp = 1; + resp_sep = cmd; + if (scanfc(fin, "%c", &cmd) < 1) + return -1; + } else if (cmd != '\\' && cmd != '?' && cmd != '_' && cmd != '#' && ispunct(cmd) && prompt) { + return 0; + } + + /* command by name */ + if (cmd == '\\') { + unsigned char cmd_name[MAXNAMSIZ], *pcmd = cmd_name; + int c_len = MAXNAMSIZ; + + if (scanfc(fin, "%c", pcmd) < 1) + return -1; + + while(c_len-- && (isalnum(*pcmd) || *pcmd == '_' )) + if (scanfc(fin, "%c", ++pcmd) < 1) + return -1; + + *pcmd = '\0'; + cmd = parse_arg((char *)cmd_name); + break; + } + + if (cmd == 0x0a || cmd == 0x0d) { + if (last_was_ret) { + if (prompt) { + fprintf(fout, "? for help, q to quit.\n"); + fprintf_flush(fout, "\nRig command: "); + } + return 0; + } + last_was_ret = 1; + } + } while (cmd == 0x0a || cmd == 0x0d); + + last_was_ret = 0; + + /* comment line */ + if (cmd == '#') { + while( cmd != '\n' && cmd != '\r') + if (scanfc(fin, "%c", &cmd) < 1) + return -1; + return 0; } - } while (cmd == 0x0a || cmd == 0x0d); + if (cmd == 'Q' || cmd == 'q') + return 1; + if (cmd == '?') { + usage_rig(fout); + fflush(fout); + return 0; + } + } else { + /* parse rest of command line */ + if (optind >= argc) + return 1; + if (argv[optind][1] == '\0') + cmd = argv[optind][0]; + else + cmd = parse_arg(argv[optind]); + optind++; + } - last_was_ret = 0; - - /* comment line */ - if (cmd == '#') { - while( cmd != '\n' && cmd != '\r') - if (scanfc(fin, "%c", &cmd) < 1) - return -1; + cmd_entry = find_cmd_entry(cmd); + if (!cmd_entry) { + fprintf(stderr, "Command '%c' not found!\n", cmd); return 0; } - if (cmd == 'Q' || cmd == 'q') - return 1; - if (cmd == '?') { + + if (!(cmd_entry->flags & ARG_NOVFO) && vfo_mode) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "VFO: "); + if (scanfc(fin, "%s", arg1) < 1) + return -1; + vfo = rig_parse_vfo(arg1); + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + vfo = rig_parse_vfo(argv[optind++]); + } + } + + if ((cmd_entry->flags & ARG_IN_LINE) && + (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + if (interactive) { + char *nl; + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg1); + if (fgets(arg1, MAXARGSZ, fin) == NULL) + return -1; + if (arg1[0] == 0xa) + if (fgets(arg1, MAXARGSZ, fin) == NULL) + return -1; + nl = strchr(arg1, 0xa); + if (nl) *nl = '\0'; /* chomp */ + p1 = arg1[0] == ' ' ? arg1 + 1 : arg1; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p1 = argv[optind++]; + } + } else + if ((cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg1); + if (scanfc(fin, "%s", arg1) < 1) + return -1; + p1 = arg1; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p1 = argv[optind++]; + } + } + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN2) && cmd_entry->arg2) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg2); + if (scanfc(fin, "%s", arg2) < 1) + return -1; + p2 = arg2; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p2 = argv[optind++]; + } + } + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN3) && cmd_entry->arg3) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg3); + if (scanfc(fin, "%s", arg3) < 1) + return -1; + p3 = arg3; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p3 = argv[optind++]; + } + } + } + +#ifdef HAVE_LIBREADLINE + + if (interactive && prompt && have_rl) { + int j, x; + + rl_instream = fin; + rl_outstream = fout; + + rp_getline("\nRig command: "); + + /* EOF (Ctl-D) received on empty input line, bail out gracefully. */ + if (!input_line) { + fprintf_flush(fout, "\n"); + return 1; + } + + /* Q or q to quit */ + if (!(strncasecmp(input_line, "q", 1))) + return 1; + + /* '?' for help */ + if (!(strncmp(input_line, "?", 1))) { usage_rig(fout); fflush(fout); return 0; } - } else { - /* parse rest of command line */ - if (optind >= argc) + + /* '#' for comment */ + if (!(strncmp(input_line, "#", 1))) + return 0; + + /* Blank line entered */ + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + rig_debug(RIG_DEBUG_BUG, "%s: input_line: %s\n", __func__, input_line); + + /* Split input_line on any number of spaces to get the command token + * Tabs are intercepted by readline for completion and a newline + * causes readline to return the typed text. If more than one + * argument is given, it will be parsed out later. + */ + result = strtok(input_line, " "); + + /* parsed_input stores pointers into input_line where the token strings + * start. + */ + if (result) { + parsed_input[0] = result; + } else { + /* Oops! Invoke GDB!! */ + fprintf_flush(fout, "\n"); return 1; - if (argv[optind][1] == '\0') - cmd = argv[optind][0]; - else - cmd = parse_arg(argv[optind]); - optind++; - } - - cmd_entry = find_cmd_entry(cmd); - if (!cmd_entry) { - fprintf(stderr, "Command '%c' not found!\n", cmd); - return 0; - } - - p1 = p2 = p3 = NULL; - if (!(cmd_entry->flags & ARG_NOVFO) && vfo_mode) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "VFO: "); - if (scanfc(fin, "%s", arg1) < 1) - return -1; - vfo = rig_parse_vfo(arg1); - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - vfo = rig_parse_vfo(argv[optind++]); } - } - if ((cmd_entry->flags & ARG_IN_LINE) && - (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { - if (interactive) { - char *nl; - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg1); - if (fgets(arg1, MAXARGSZ, fin) == NULL) - return -1; - if (arg1[0] == 0xa) - if (fgets(arg1, MAXARGSZ, fin) == NULL) - return -1; - nl = strchr(arg1, 0xa); - if (nl) *nl = '\0'; /* chomp */ - p1 = arg1[0] == ' ' ? arg1 + 1 : arg1; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p1 = argv[optind++]; + /* At this point parsed_input contains the typed text of the command + * with surrounding space characters removed. + */ + + /* Single character command */ + if ((strlen(parsed_input[0]) == 1) && (*parsed_input[0] != '\\')) { + cmd = *parsed_input[0]; } - } else - if ((cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg1); - if (scanfc(fin, "%s", arg1) < 1) - return -1; + /* Test the command token, parsed_input[0] */ + else if ((*parsed_input[0] == '\\') && (strlen(parsed_input[0]) > 1)) { + char cmd_name[MAXNAMSIZ]; + + /* if there is no terminating '\0' character in the source string, + * srncpy() doesn't add one even if the supplied length is less + * than the destination array. Truncate the source string here. + */ + if (strlen(parsed_input[0] + 1) >= MAXNAMSIZ) + *(parsed_input[0] + MAXNAMSIZ) = '\0'; + + /* The starting position of the source string is the first + * character past the initial '\'. Using MAXNAMSIZ for the + * length leaves enough space for the '\0' string terminator in the + * cmd_name array. + */ + strncpy(cmd_name, parsed_input[0] + 1, MAXNAMSIZ); + + /* Sanity check as valid multiple character commands consist of + * alpha-numeric characters and the underscore ('_') character. + */ + for (j = 0; cmd_name[j] != '\0'; j++) { + if (!(isalnum(cmd_name[j]) || cmd_name[j] == '_')) { + fprintf(stderr, "Valid multiple character command names contain alpha-numeric characters plus '_'\n"); + return 0; + } + } + + cmd = parse_arg(cmd_name); + } + /* Single '\' entered, prompt again */ + else if ((*parsed_input[0] == '\\') && (strlen(parsed_input[0]) == 1)) { + return 0; + } + /* Multiple characters but no leading '\' */ + else { + fprintf(stderr, "Precede multiple character command names with '\\'\n"); + return 0; + } + + cmd_entry = find_cmd_entry(cmd); + if (!cmd_entry) { + if (cmd == '\0') + fprintf(stderr, "Command '%s' not found!\n", parsed_input[0]); + else + fprintf(stderr, "Command '%c' not found!\n", cmd); + + return 0; + } + + /* If vfo_mode is enabled (-o|--vfo) check if already given + * or prompt for it. + */ + if (!(cmd_entry->flags & ARG_NOVFO) && vfo_mode) { + /* Check if VFO was given with command. */ + result = strtok(NULL, " "); + + if (result) { + x = 1; + parsed_input[x] = result; + } + /* Need to prompt if a VFO string was not given. */ + else { + x = 0; + rp_getline("VFO: "); + + if (!input_line) { + fprintf_flush(fout, "\n"); + return 1; + } + + /* Blank line entered */ + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + /* Get the first token of input, the rest, if any, will be + * used later. + */ + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + /* VFO name tokens are presently quite short. Truncate excessively + * long strings. + */ + if (strlen(parsed_input[x]) >= MAXNAMSIZ) + *(parsed_input[x] + (MAXNAMSIZ - 1)) = '\0'; + + /* Sanity check, VFO names are alpha only. */ + for (j = 0; j < MAXNAMSIZ && parsed_input[x][j] != '\0'; j++) { + if (!(isalpha(parsed_input[x][j]))) { + parsed_input[x][j] = '\0'; + + break; + } + } + vfo = rig_parse_vfo(parsed_input[x]); + + if (vfo == RIG_VFO_NONE) { + fprintf(stderr, "Warning: VFO '%s' unrecognized, using 'currVFO' instead.\n", + parsed_input[x]); + vfo = RIG_VFO_CURR; + } + } + + /* \send_cmd, \send_morse */ + if ((cmd_entry->flags & ARG_IN_LINE) && + (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + /* Check for a non-existent delimiter so as to not break up + * remaining line into separate tokens (spaces OK). + */ + result = strtok(NULL, "\0"); + + if (vfo_mode && result) { + x = 2; + parsed_input[x] = result; + } else if (result) { + x = 1; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg1) + 3)]; + + strcpy(pmptstr, cmd_entry->arg1); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + /* Blank line entered */ + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + if (input_line) + parsed_input[x] = input_line; + else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + /* The arg1 array size is MAXARGSZ + 1 so truncate it to fit if larger. */ + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg1, parsed_input[x]); p1 = arg1; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p1 = argv[optind++]; } - } - if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN2) && cmd_entry->arg2) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg2); - if (scanfc(fin, "%s", arg2) < 1) - return -1; + + /* Normal argument parsing. */ + else if ((cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + result = strtok(NULL, " "); + + if (vfo_mode && result) { + x = 2; + parsed_input[x] = result; + } else if (result) { + x = 1; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg1) + 3)]; + + strcpy(pmptstr, cmd_entry->arg1); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg1, parsed_input[x]); + p1 = arg1; + } + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN2) && cmd_entry->arg2) { + result = strtok(NULL, " "); + + if (vfo_mode && result) { + x = 3; + parsed_input[x] = result; + } else if (result) { + x = 2; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg2) + 3)]; + + strcpy(pmptstr, cmd_entry->arg2); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg2, parsed_input[x]); p2 = arg2; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p2 = argv[optind++]; } - } - if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN3) && cmd_entry->arg3) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg3); - if (scanfc(fin, "%s", arg3) < 1) - return -1; + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN3) && cmd_entry->arg3) { + result = strtok(NULL, " "); + + if (vfo_mode && result) { + x = 4; + parsed_input[x] = result; + } else if (result) { + x = 3; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg3) + 3)]; + + strcpy(pmptstr, cmd_entry->arg3); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg3, parsed_input[x]); p3 = arg3; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p3 = argv[optind++]; } } +#endif /* HAVE_LIBREADLINE */ + + /* * mutex locking needed because rigctld is multithreaded * and hamlib is not MT-safe @@ -645,6 +1050,8 @@ void usage_rig(FILE *fout) else fprintf(fout, ")%*s", nbspaces, " "); } + + fprintf(fout, "\n\nPrepend long command names with '\\', e.g. '\\dump_state'\n"); } From 978a26968998216cf6c4e249d734c357a8f81c49 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Mon, 18 Feb 2013 20:42:42 -0600 Subject: [PATCH 03/10] rigctl_parse.c: Implement history recall Implement first cut at storing and recalling history. At this point history is retained for the current session only. History is stored as complete command lines even if values are entered at separate prompts. Readline allows editing and even deleting recalled history lines. --- tests/rigctl.c | 16 ++++++++++ tests/rigctl_parse.c | 75 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/tests/rigctl.c b/tests/rigctl.c index f53904d88..edf85e814 100644 --- a/tests/rigctl.c +++ b/tests/rigctl.c @@ -48,6 +48,19 @@ extern char *readline (); /* 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 */ + #include #include "misc.h" @@ -351,6 +364,9 @@ int main (int argc, char *argv[]) if (interactive && prompt && have_rl) { rl_readline_name = "rigctl"; +#ifdef HAVE_READLINE_HISTORY + using_history(); +#endif } #endif /* HAVE_LIBREADLINE */ diff --git a/tests/rigctl_parse.c b/tests/rigctl_parse.c index 11bb989d7..c822f7272 100644 --- a/tests/rigctl_parse.c +++ b/tests/rigctl_parse.c @@ -48,6 +48,19 @@ extern char *readline (); /* 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 */ + #include #include "misc.h" @@ -90,6 +103,11 @@ static char *input_line = (char *)NULL; static char *result = (char *)NULL; static char *parsed_input[sizeof(char) * 5]; static const int have_rl = 1; + +#ifdef HAVE_READLINE_HISTORY +static char *rp_hist_buf = (char *)NULL; +#endif + #else /* no readline */ static const int have_rl = 0; #endif @@ -605,6 +623,13 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (interactive && prompt && have_rl) { int j, x; +#ifdef HAVE_READLINE_HISTORY + /* Minimum space for 32+1+32+1+128+1+128+1+128+1 = 453 chars, so + * allocate 512 chars cleared to zero for safety. + */ + rp_hist_buf = (char *)calloc(512, sizeof(char)); +#endif + rl_instream = fin; rl_outstream = fout; @@ -659,12 +684,19 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) } /* At this point parsed_input contains the typed text of the command - * with surrounding space characters removed. + * with surrounding space characters removed. If Readline History is + * available, copy the command string into a history buffer. */ /* Single character command */ if ((strlen(parsed_input[0]) == 1) && (*parsed_input[0] != '\\')) { cmd = *parsed_input[0]; + +#ifdef HAVE_READLINE_HISTORY + /* Store what is typed, not validated, for history. */ + if (rp_hist_buf) + strncpy(rp_hist_buf, parsed_input[0], 1); +#endif } /* Test the command token, parsed_input[0] */ else if ((*parsed_input[0] == '\\') && (strlen(parsed_input[0]) > 1)) { @@ -677,6 +709,10 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[0] + 1) >= MAXNAMSIZ) *(parsed_input[0] + MAXNAMSIZ) = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) + strncpy(rp_hist_buf, parsed_input[0], MAXNAMSIZ); +#endif /* The starting position of the source string is the first * character past the initial '\'. Using MAXNAMSIZ for the * length leaves enough space for the '\0' string terminator in the @@ -763,6 +799,12 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) >= MAXNAMSIZ) *(parsed_input[x] + (MAXNAMSIZ - 1)) = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXNAMSIZ); + } +#endif /* Sanity check, VFO names are alpha only. */ for (j = 0; j < MAXNAMSIZ && parsed_input[x][j] != '\0'; j++) { if (!(isalpha(parsed_input[x][j]))) { @@ -822,6 +864,12 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg1, parsed_input[x]); p1 = arg1; } @@ -864,6 +912,12 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg1, parsed_input[x]); p1 = arg1; } @@ -904,6 +958,12 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg2, parsed_input[x]); p2 = arg2; } @@ -944,9 +1004,22 @@ int rigctl_parse(RIG *my_rig, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg3, parsed_input[x]); p3 = arg3; } +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + add_history(rp_hist_buf); + free(rp_hist_buf); + rp_hist_buf = (char *)NULL; + } +#endif } #endif /* HAVE_LIBREADLINE */ From 9f342cfd23ef8e253888f906f3c9de591e22d0f0 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Wed, 20 Feb 2013 11:48:27 -0600 Subject: [PATCH 04/10] rigctl.c: Add options to save/read command history As commands from a previous session may not be desired, make reading the history file at rigctl start and writing to it at rigctl close optional by use of the -i/--read_history or -I/--save-history options. Compilation is conditional on having Readline and Readline History support detected at build system configuration time. History is stored in '$HOME/.rigctl_history' by default. An alternate path may be specified by setting the RIGCTL_HIST_DIR environment variable: $ RIGCTL_HIST_DIR=~/.rigctl rigctl -iI will temporarily set the history directory to /home/USER/.rigctl and create .rigctl_history if it does not exist. If the file cannot be read or written a warning message will be given on stderr. --- tests/rigctl.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/tests/rigctl.c b/tests/rigctl.c index edf85e814..1adaea93f 100644 --- a/tests/rigctl.c +++ b/tests/rigctl.c @@ -49,6 +49,8 @@ extern char *readline (); #endif /* HAVE_LIBREADLINE */ #ifdef HAVE_READLINE_HISTORY +# include +# define HST_SHRT_OPTS "iI" # if defined(HAVE_READLINE_HISTORY_H) # include # elif defined(HAVE_HISTORY_H) @@ -58,7 +60,9 @@ extern void add_history (); extern int write_history (); extern int read_history (); # endif /* defined(HAVE_READLINE_HISTORY_H) */ - /* no history */ +#else +/* no history */ +#define HST_SHRT_OPTS "" #endif /* HAVE_READLINE_HISTORY */ @@ -100,6 +104,10 @@ static struct option long_options[] = {"show-conf", 0, 0, 'L'}, {"dump-caps", 0, 0, 'u'}, {"vfo", 0, 0, 'o'}, +#ifdef HAVE_READLINE_HISTORY + {"read-history", 0, 0, 'i'}, + {"save-history", 0, 0, 'I'}, +#endif {"verbose", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {"version", 0, 0, 'V'}, @@ -133,6 +141,14 @@ int main (int argc, char *argv[]) int verbose = 0; int show_conf = 0; int dump_caps_opt = 0; +#ifdef HAVE_READLINE_HISTORY + int rd_hist = 0; + int sv_hist = 0; + const char *hist_dir = NULL; + const char hist_file[] = "/.rigctl_history"; + char *hist_path = NULL; + struct stat hist_dir_stat; +#endif const char *rig_file=NULL, *ptt_file=NULL, *dcd_file=NULL; ptt_type_t ptt_type = RIG_PTT_NONE; dcd_type_t dcd_type = RIG_DCD_NONE; @@ -144,7 +160,7 @@ int main (int argc, char *argv[]) int c; int option_index = 0; - c = getopt_long (argc, argv, SHORT_OPTIONS, + c = getopt_long (argc, argv, SHORT_OPTIONS HST_SHRT_OPTS, long_options, &option_index); if (c == -1) break; @@ -262,6 +278,14 @@ int main (int argc, char *argv[]) case 'o': vfo_mode++; break; +#ifdef HAVE_READLINE_HISTORY + case 'i': + rd_hist++; + break; + case 'I': + sv_hist++; + break; +#endif case 'v': verbose++; break; @@ -361,14 +385,31 @@ int main (int argc, char *argv[]) exitcode = 0; #ifdef HAVE_LIBREADLINE - if (interactive && prompt && have_rl) { rl_readline_name = "rigctl"; #ifdef HAVE_READLINE_HISTORY - using_history(); + using_history(); /* Initialize Readline History */ + + if (rd_hist || sv_hist) { + if (!(hist_dir = getenv("RIGCTL_HIST_DIR"))) + hist_dir = getenv("HOME"); + + if (((stat(hist_dir, &hist_dir_stat) == -1) && (errno == ENOENT)) + || !(S_ISDIR(hist_dir_stat.st_mode))) { + fprintf(stderr, "Warning: %s is not a directory!\n", hist_dir); + } + + hist_path = (char *)calloc((sizeof(char) * (strlen(hist_dir) + strlen(hist_file) + 1)), sizeof(char)); + + strncpy(hist_path, hist_dir, strlen(hist_dir)); + strncat(hist_path, hist_file, strlen(hist_file)); + } + + if (rd_hist && hist_path) + if (read_history(hist_path) == ENOENT) + fprintf(stderr, "Warning: Could not read history from %s\n", hist_path); #endif } - #endif /* HAVE_LIBREADLINE */ do { @@ -378,6 +419,20 @@ int main (int argc, char *argv[]) } while (retcode == 0 || retcode == 2); +#ifdef HAVE_LIBREADLINE + if (interactive && prompt && have_rl) { +#ifdef HAVE_READLINE_HISTORY + if (sv_hist && hist_path) + if (write_history(hist_path) == ENOENT) + fprintf(stderr, "\nWarning: Could not write history to %s\n", hist_path); + + if ((rd_hist || sv_hist) && hist_path) { + free(hist_path); + hist_path = (char *)NULL; + } + } +#endif +#endif rig_close(my_rig); /* close port */ rig_cleanup(my_rig); /* if you care about memory */ @@ -406,6 +461,10 @@ void usage(void) " -l, --list list all model numbers and exit\n" " -u, --dump-caps dump capabilities and exit\n" " -o, --vfo do not default to VFO_CURR, require extra vfo arg\n" +#ifdef HAVE_READLINE_HISTORY + " -i, --read-history read prior interactive session history\n" + " -I, --save-history save current interactive session history\n" +#endif " -v, --verbose set verbose mode, cumulative\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n\n" From 991d1ea24c9dff0eb48ee968c38675a6f87a91d2 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Wed, 20 Feb 2013 15:05:45 -0600 Subject: [PATCH 05/10] rigctl.1: Document Readline and history additions Document Readline and history additions to rigctl along with new -i/--read-history and -I/--save-history options. Document use of RIGCTL_HIST_DIR environment variable to set an alternate path for the .rigctl_history file. Other minor edits. --- NEWS | 2 ++ tests/rigctl.1 | 74 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 0da6842a0..751ffb948 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,8 @@ Version 3.0 * IC-PCR1500/2500 default write_delay to 0, IC-746/756, IC-PCR8500 fixes, pcr.c, pcr1500.c: Add DSP support. TNX Paul, KE7ZZ * WinRadio G313 updates. TNX Julian Campbel + * Readline editing and history support added to rigctl interactive + mode. Implement options for reading and writing history file. Version 1.2.15.3 2012-11-01 diff --git a/tests/rigctl.1 b/tests/rigctl.1 index 9dd946b7a..145897c08 100644 --- a/tests/rigctl.1 +++ b/tests/rigctl.1 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH RIGCTL "1" "January 23, 2013" "Hamlib" "Radio Control Program" +.TH RIGCTL "1" "February 20, 2013" "Hamlib" "Radio Control Program" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -31,14 +31,14 @@ interactive mode if none are provided on the command line. Keep in mind that \fBHamlib\fP is BETA level software. While a lot of backend libraries lack complete rig support, the basic functions are usually well supported. The API may change without publicized notice, -while an advancement of the minor version (e.g. 1.1.x to 1.2.x) indicates such +while an advancement of the major version (e.g. 1.x to 3.x) indicates such a change. .PP Please report bugs and provide feedback at the e-mail address given in the REPORTING BUGS section. Patches and code enhancements are also welcome. .SH OPTIONS This program follows the usual GNU command line syntax, with long -options starting with two dashes (`-'). +options starting with two dashes ('-'). .PP Here is a summary of the supported options: .TP @@ -120,7 +120,27 @@ Dump capabilities for the radio defined with -m above and exit. .TP .B \-o, --vfo Set vfo mode, requiring an extra VFO argument in front of each appropriate -command. Otherwise, VFO_CURR is assumed when this option is not set. +command. Otherwise, 'currVFO' is assumed when this option is not set. +.TP +.B \-i, --read-history +Read previously saved command and argument history from a file +(default '~/.rigctl_history') for the current session. Available when +\fBrigctl\fP is built with Readline support (see READLINE below). +.sp +\fBN.B.\fP To read a history file stored in another directory, set the +RIGCTL_HIST_DIR environment variable, e.g. 'RIGCTL_HIST_DIR=~/tmp rigctl -i'. +When RIGCTL_HIST_DIR is not set, the value of HOME is used. +.TP +.B \-I, --save-history +Write current session and previous session(s), if -i option is given, command and +argument history to a file (default '~/.rigctl_history') at the end of the current +session. Complete commands with arguments are saved as a single line to be +recalled and used or edited. Available when \fBrigctl\fP is built with Readline +support (see READLINE below). +.sp +\fBN.B.\fP To write a history file in another directory, set the RIGCTL_HIST_DIR +environment variable, e.g. 'RIGCTL_HIST_DIR=~/tmp rigctl -I'. When RIGCTL_HIST_DIR +is not set, the value of HOME is used. .TP .B \-v, --verbose Set verbose mode, cumulative (see DIAGNOSTICS below). @@ -142,7 +162,10 @@ the operation will fail with a \fBHamlib\fP error code. Commands can be entered either as a single char, or as a long command name. Basically, the commands do not take a dash in front of them on the command line, as the options do. They may be typed in when in interactive mode -or provided as argument(s) in command line interface mode. +or provided as argument(s) in command line interface mode. In interactive +mode commands and their arguments may be entered on a single line: +.sp +Rig command: M LSB 2400 .PP Since most of the \fBHamlib\fP operations have a \fIset\fP and a \fIget\fP method, an upper case letter will be used for \fIset\fP method whereas the @@ -150,7 +173,7 @@ corresponding lower case letter refers to the \fIget\fP method. Each operation also has a long name; in interactive mode, prepend a backslash to enter a long command name. .sp -Example: Use "\\dump_caps" to see what this radio can do. +Example: Use '\\dump_caps' to see what this radio and backend support. .PP Please note that the backend for the radio to be controlled, or the radio itself may not support some commands. In that case, the operation will fail @@ -509,6 +532,45 @@ Connect to a running \fBrigctld\fP with rig model 2 ("NET rigctl") on the local host and specifying the TCP port, setting frequency and mode: .sp $ rigctl -m 2 -r localhost:4532 F 7253500 M LSB 0 +.SH READLINE +If Readline library development files are found at configure time, \fBrigctl\fP +will be conditonally built with Readline support for command and argument entry. +Readline command key bindings are at their defaults as described in the Readline +manual (\fIhttp://cnswww.cns.cwru.edu/php/chet/readline/rluserman.html\fP) +although \fBrigctl\fP sets the name 'rigctl' which can be used in Conditional +Init Constructs in the Readline Init File ('~/.inputrc' by default) for custom +keybindings unique to \fBrigctl\fP. + +Command history is available with Readline support as described in the Readline +History manual +(\fIhttp://cnswww.cns.cwru.edu/php/chet/readline/history.html#SEC1\fP). Command +and argument strings are stored as single lines even when arguments are prompted +for input individually. Commands and arguments are not validated and are stored +as typed with values separated by a single space. + +Normally session history is not saved, however, use of either of the +\fI-i/--read-history\fP or \fI-I/--save-history\fP options when starting +\fBrigctl\fP will cause any previously saved history to be read in and/or the +current and any previous session history (assuming the -i and -I options are +given together) will be written out when \fBrigctl\fP is closed. Each option is +mutually exclusive, i.e. either may be given separately or in combination. This +is useful to save a set of commands and then read them later but not write the +modified history for a consistent set of test commands in interactive mode, for +example. + +History is stored in '~/.rigctl_history' by default although the destination +directory may be changed by setting the RIGCTL_HIST_DIR environment variable. +When RIGCTL_HIST_DIR is unset, the value of the HOME environment variable is +used instead. Only the destination directory may be changed at this time. + +If Readline support is not found at configure time the original internal command +handler is used. Readline is not used for \fBrigctl\fP commands entered on the +command line regardless if Readline support is built in or not. + +\fBN.B.\fP Readline support is not included in the Windows 32 binary builds +supplied by the Hamlib Project. Running \fBrigctl\fP on the Windows 32 platform +in the 'cmd' shell does give session command line history, however, it is not +saved to disk between sessions. .SH DIAGNOSTICS The \fB-v\fP, \fB--verbose\fP option allows different levels of diagnostics to be output to \fBstderr\fP and correspond to -v for BUG, -vv for ERR, From e549fee11a6e4b4f96eefa9019400f8e38da4cc0 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Fri, 22 Feb 2013 17:18:30 -0600 Subject: [PATCH 06/10] rotctl_parse.c: Implement readline interactive mode Initial implementation of Readline input handling. Only if 'configure' finds Readline will it be enabled and when enabled it will only be used by rotctl in interactive mode. Passing rotator commands from the rotctl command line and rotctld use the original input handling which has not been modified. --- tests/rotctl.c | 27 +- tests/rotctl_parse.c | 691 +++++++++++++++++++++++++++++++++---------- 2 files changed, 557 insertions(+), 161 deletions(-) diff --git a/tests/rotctl.c b/tests/rotctl.c index a4ff21687..068e2cb36 100644 --- a/tests/rotctl.c +++ b/tests/rotctl.c @@ -33,9 +33,20 @@ #include #include #include - #include +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) /* !defined(HAVE_READLINE_READLINE_H) */ +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* HAVE_READLINE_H */ +#else +/* no readline */ +#endif /* HAVE_LIBREADLINE */ + #include #include "misc.h" @@ -71,6 +82,14 @@ static struct option long_options[] = #define MAXCONFLEN 128 +/* variable for readline support */ +#ifdef HAVE_LIBREADLINE +static const int have_rl = 1; +#else /* no readline */ +static const int have_rl = 0; +#endif + + int interactive = 1; /* if no cmd on command line, switch to interactive */ int prompt = 1; /* Print prompt in rotctl */ @@ -233,6 +252,12 @@ int main (int argc, char *argv[]) exitcode = 0; +#ifdef HAVE_LIBREADLINE + if (interactive && prompt && have_rl) { + rl_readline_name = "rigctl"; + } +#endif /* HAVE_LIBREADLINE */ + do { retcode = rotctl_parse(my_rot, stdin, stdout, argv, argc); if (retcode == 2) diff --git a/tests/rotctl_parse.c b/tests/rotctl_parse.c index d6727ee2d..1fee435ba 100644 --- a/tests/rotctl_parse.c +++ b/tests/rotctl_parse.c @@ -33,9 +33,20 @@ #include #include #include - #include +#ifdef HAVE_LIBREADLINE +# if defined(HAVE_READLINE_READLINE_H) +# include +# elif defined(HAVE_READLINE_H) /* !defined(HAVE_READLINE_READLINE_H) */ +# include +# else /* !defined(HAVE_READLINE_H) */ +extern char *readline (); +# endif /* HAVE_READLINE_H */ +#else +/* no readline */ +#endif /* HAVE_LIBREADLINE */ + #include #include "serial.h" #include "misc.h" @@ -70,6 +81,16 @@ static pthread_mutex_t rot_mutex = PTHREAD_MUTEX_INITIALIZER; #define ARG_IN (ARG_IN1|ARG_IN2|ARG_IN3|ARG_IN4) #define ARG_OUT (ARG_OUT1|ARG_OUT2|ARG_OUT3|ARG_OUT4) +/* variables for readline support */ +#ifdef HAVE_LIBREADLINE +static char *input_line = (char *)NULL; +static char *result = (char *)NULL; +static char *parsed_input[sizeof(char) * 7]; +static const int have_rl = 1; +#else /* no readline */ +static const int have_rl = 0; +#endif + struct test_table { unsigned char cmd; const char *name; @@ -212,6 +233,38 @@ void hash_delete_all() { } +#ifdef HAVE_LIBREADLINE +/* Frees allocated memory and sets pointers to NULL before calling readline + * and then parses the input into space separated tokens. + */ +static void rp_getline(const char *s) +{ + int i; + + /* free allocated memory and set pointers to NULL */ + if (input_line) { + free(input_line); + input_line = (char *)NULL; + } + + if (result) { + result = (char *)NULL; + } + + /* cmd, arg1, arg2, arg3, arg4, arg5, arg6 + * arg5 and arg 6 are currently unused. + */ + for (i = 0; i < 7; i++) + parsed_input[i] = NULL; + + /* Action! Returns typed line with newline stripped. */ + input_line = readline(s); +} + + +#endif + + /* * TODO: use Lex? */ @@ -264,194 +317,510 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) unsigned char cmd; struct test_table *cmd_entry; - char arg1[MAXARGSZ + 1], *p1; - char arg2[MAXARGSZ + 1], *p2; - char arg3[MAXARGSZ + 1], *p3; - char arg4[MAXARGSZ + 1], *p4; - char *p5, *p6; + char arg1[MAXARGSZ + 1], *p1 = NULL; + char arg2[MAXARGSZ + 1], *p2 = NULL; + char arg3[MAXARGSZ + 1], *p3 = NULL; + char arg4[MAXARGSZ + 1], *p4 = NULL; + char *p5 = NULL; + char *p6 = NULL; static int last_was_ret = 1; - if (interactive) { - if (prompt) - fprintf_flush(fout, "\nRotator command: "); + /* cmd, internal, rotctld */ + if (!(interactive && prompt && have_rl)) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "\nRotator command: "); - do { - if (scanfc(fin, "%c", &cmd) < 1) - return -1; - - /* Extended response protocol requested with leading '+' on command - * string--rotctld only! - */ - if (cmd == '+' && !prompt) { - ext_resp = 1; + do { if (scanfc(fin, "%c", &cmd) < 1) return -1; - } else if (cmd == '+' && prompt) { - return 0; - } - if (cmd != '\\' && cmd != '_' && cmd != '#' && ispunct(cmd) && !prompt) { - ext_resp = 1; - resp_sep = cmd; - if (scanfc(fin, "%c", &cmd) < 1) - return -1; - } else if (cmd != '\\' && cmd != '?' && cmd != '_' && cmd != '#' && ispunct(cmd) && prompt) { - return 0; - } - - /* command by name */ - if (cmd == '\\') { - unsigned char cmd_name[MAXNAMSIZ], *pcmd = cmd_name; - int c_len = MAXNAMSIZ; - - if (scanfc(fin, "%c", pcmd) < 1) - return -1; - - while(c_len-- && (isalnum(*pcmd) || *pcmd == '_' )) - if (scanfc(fin, "%c", ++pcmd) < 1) + /* Extended response protocol requested with leading '+' on command + * string--rotctld only! + */ + if (cmd == '+' && !prompt) { + ext_resp = 1; + if (scanfc(fin, "%c", &cmd) < 1) return -1; - - *pcmd = '\0'; - cmd = parse_arg((char *) cmd_name); - break; - } - - if (cmd == 0x0a || cmd == 0x0d) { - if (last_was_ret) { - if (prompt) { - fprintf_flush(fout, "? for help, q to quit.\n"); - } + } else if (cmd == '+' && prompt) { return 0; } - last_was_ret = 1; + + if (cmd != '\\' && cmd != '_' && cmd != '#' && ispunct(cmd) && !prompt) { + ext_resp = 1; + resp_sep = cmd; + if (scanfc(fin, "%c", &cmd) < 1) + return -1; + } else if (cmd != '\\' && cmd != '?' && cmd != '_' && cmd != '#' && ispunct(cmd) && prompt) { + return 0; + } + + /* command by name */ + if (cmd == '\\') { + unsigned char cmd_name[MAXNAMSIZ], *pcmd = cmd_name; + int c_len = MAXNAMSIZ; + + if (scanfc(fin, "%c", pcmd) < 1) + return -1; + + while(c_len-- && (isalnum(*pcmd) || *pcmd == '_' )) + if (scanfc(fin, "%c", ++pcmd) < 1) + return -1; + + *pcmd = '\0'; + cmd = parse_arg((char *) cmd_name); + break; + } + + if (cmd == 0x0a || cmd == 0x0d) { + if (last_was_ret) { + if (prompt) { + fprintf_flush(fout, "? for help, q to quit.\n"); + } + return 0; + } + last_was_ret = 1; + } + } while (cmd == 0x0a || cmd == 0x0d); + + last_was_ret = 0; + + /* comment line */ + if (cmd == '#') { + while( cmd != '\n' && cmd != '\r') + if (scanfc(fin, "%c", &cmd) < 1) + return -1; + return 0; } - } while (cmd == 0x0a || cmd == 0x0d); + if (cmd == 'Q' || cmd == 'q') + return 1; + if (cmd == '?') { + usage_rot(fout); + fflush(fout); + return 0; + } + } else { + /* parse rest of command line */ + if (optind >= argc) + return 1; + if (argv[optind][1] == '\0') + cmd = argv[optind][0]; + else + cmd = parse_arg(argv[optind]); + optind++; + } - last_was_ret = 0; - - /* comment line */ - if (cmd == '#') { - while( cmd != '\n' && cmd != '\r') - if (scanfc(fin, "%c", &cmd) < 1) - return -1; + cmd_entry = find_cmd_entry(cmd); + if (!cmd_entry) { + fprintf_flush(stderr, "Command '%c' not found!\n", cmd); return 0; } - if (cmd == 'Q' || cmd == 'q') + + if ((cmd_entry->flags & ARG_IN_LINE) && + (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + if (interactive) { + char *nl; + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg1); + if (fgets(arg1, MAXARGSZ, fin) == NULL) + return -1; + if (arg1[0] == 0xa) + if (fgets(arg1, MAXARGSZ, fin) == NULL) + return -1; + nl = strchr(arg1, 0xa); + if (nl) *nl = '\0'; /* chomp */ + p1 = arg1[0]==' '?arg1+1:arg1; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p1 = argv[optind++]; + } + } else if ((cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg1); + if (scanfc(fin, "%s", arg1) < 1) + return -1; + p1 = arg1; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p1 = argv[optind++]; + } + } + if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN2) && cmd_entry->arg2) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg2); + if (scanfc(fin, "%s", arg2) < 1) + return -1; + p2 = arg2; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p2 = argv[optind++]; + } + } + if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN3) && cmd_entry->arg3) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg3); + if (scanfc(fin, "%s", arg3) < 1) + return -1; + p3 = arg3; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p3 = argv[optind++]; + } + } + + if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN4) && cmd_entry->arg4) { + if (interactive) { + if (prompt) + fprintf_flush(fout, "%s: ", cmd_entry->arg4); + if (scanfc(fin, "%s", arg4) < 1) + return -1; + p4 = arg4; + } else { + if (!argv[optind]) { + fprintf(stderr, "Invalid arg for command '%s'\n", + cmd_entry->name); + exit(1); + } + p4 = argv[optind++]; + } + } + } + +#ifdef HAVE_LIBREADLINE + + if (interactive && prompt && have_rl) { + int j, x; + + rl_instream = fin; + rl_outstream = fout; + + rp_getline("\nRotator command: "); + + /* EOF (Ctl-D) received on empty input line, bail out gracefully. */ + if (!input_line) { + fprintf_flush(fout, "\n"); return 1; - if (cmd == '?') { + } + + /* Q or q to quit */ + if (!(strncasecmp(input_line, "q", 1))) + return 1; + + /* '?' for help */ + if (!(strncmp(input_line, "?", 1))) { usage_rot(fout); fflush(fout); return 0; } - } else { - /* parse rest of command line */ - if (optind >= argc) + + /* '#' for comment */ + if (!(strncmp(input_line, "#", 1))) + return 0; + + /* Blank line entered */ + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + rig_debug(RIG_DEBUG_BUG, "%s: input_line: %s\n", __func__, input_line); + + /* Split input_line on any number of spaces to get the command token + * Tabs are intercepted by readline for completion and a newline + * causes readline to return the typed text. If more than one + * argument is given, it will be parsed out later. + */ + result = strtok(input_line, " "); + + /* parsed_input stores pointers into input_line where the token strings + * start. + */ + if (result) { + parsed_input[0] = result; + } else { + /* Oops! Invoke GDB!! */ + fprintf_flush(fout, "\n"); return 1; - if (argv[optind][1] == '\0') - cmd = argv[optind][0]; - else - cmd = parse_arg(argv[optind]); - optind++; - } - - cmd_entry = find_cmd_entry(cmd); - if (!cmd_entry) { - fprintf_flush(stderr, "Command '%c' not found!\n", cmd); - return 0; - } - - p1 = p2 = p3 = p4 = p5 = p6 = NULL; - - if ((cmd_entry->flags & ARG_IN_LINE) && - (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { - if (interactive) { - char *nl; - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg1); - if (fgets(arg1, MAXARGSZ, fin) == NULL) - return -1; - if (arg1[0] == 0xa) - if (fgets(arg1, MAXARGSZ, fin) == NULL) - return -1; - nl = strchr(arg1, 0xa); - if (nl) *nl = '\0'; /* chomp */ - p1 = arg1[0]==' '?arg1+1:arg1; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p1 = argv[optind++]; } - } else if ((cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg1); - if (scanfc(fin, "%s", arg1) < 1) - return -1; + + /* At this point parsed_input contains the typed text of the command + * with surrounding space characters removed. + */ + + /* Single character command */ + if ((strlen(parsed_input[0]) == 1) && (*parsed_input[0] != '\\')) { + cmd = *parsed_input[0]; + } + + /* Test the command token, parsed_input[0] */ + else if ((*parsed_input[0] == '\\') && (strlen(parsed_input[0]) > 1)) { + char cmd_name[MAXNAMSIZ]; + + /* if there is no terminating '\0' character in the source string, + * srncpy() doesn't add one even if the supplied length is less + * than the destination array. Truncate the source string here. + */ + if (strlen(parsed_input[0] + 1) >= MAXNAMSIZ) + *(parsed_input[0] + MAXNAMSIZ) = '\0'; + + /* The starting position of the source string is the first + * character past the initial '\'. Using MAXNAMSIZ for the + * length leaves enough space for the '\0' string terminator in the + * cmd_name array. + */ + strncpy(cmd_name, parsed_input[0] + 1, MAXNAMSIZ); + + /* Sanity check as valid multiple character commands consist of + * alpha-numeric characters and the underscore ('_') character. + */ + for (j = 0; cmd_name[j] != '\0'; j++) { + if (!(isalnum(cmd_name[j]) || cmd_name[j] == '_')) { + fprintf(stderr, "Valid multiple character command names contain alpha-numeric characters plus '_'\n"); + return 0; + } + } + + cmd = parse_arg(cmd_name); + } + /* Single '\' entered, prompt again */ + else if ((*parsed_input[0] == '\\') && (strlen(parsed_input[0]) == 1)) { + return 0; + } + /* Multiple characters but no leading '\' */ + else { + fprintf(stderr, "Precede multiple character command names with '\\'\n"); + return 0; + } + + cmd_entry = find_cmd_entry(cmd); + if (!cmd_entry) { + if (cmd == '\0') + fprintf(stderr, "Command '%s' not found!\n", parsed_input[0]); + else + fprintf(stderr, "Command '%c' not found!\n", cmd); + + return 0; + } + + /* \send_cmd, \send_morse */ + if ((cmd_entry->flags & ARG_IN_LINE) && + (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + /* Check for a non-existent delimiter so as to not break up + * remaining line into separate tokens (spaces OK). + */ + result = strtok(NULL, "\0"); + + if (result) { + x = 1; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg1) + 3)]; + + strcpy(pmptstr, cmd_entry->arg1); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + /* Blank line entered */ + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + if (input_line) + parsed_input[x] = input_line; + else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + /* The arg1 array size is MAXARGSZ + 1 so truncate it to fit if larger. */ + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg1, parsed_input[x]); p1 = arg1; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p1 = argv[optind++]; } - } - if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN2) && cmd_entry->arg2) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg2); - if (scanfc(fin, "%s", arg2) < 1) - return -1; + + /* Normal argument parsing. */ + else if ((cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { + result = strtok(NULL, " "); + + if (result) { + x = 1; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg1) + 3)]; + + strcpy(pmptstr, cmd_entry->arg1); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg1, parsed_input[x]); + p1 = arg1; + } + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN2) && cmd_entry->arg2) { + result = strtok(NULL, " "); + + if (result) { + x = 2; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg2) + 3)]; + + strcpy(pmptstr, cmd_entry->arg2); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg2, parsed_input[x]); p2 = arg2; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p2 = argv[optind++]; } - } - if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN3) && cmd_entry->arg3) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg3); - if (scanfc(fin, "%s", arg3) < 1) - return -1; - p3 = arg3; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN3) && cmd_entry->arg3) { + result = strtok(NULL, " "); + + if (result) { + x = 3; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg3) + 3)]; + + strcpy(pmptstr, cmd_entry->arg3); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } } - p3 = argv[optind++]; + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg3, parsed_input[x]); + p3 = arg3; + } + if (p1 && p1[0] != '?' && (cmd_entry->flags & ARG_IN4) && cmd_entry->arg4) { + result = strtok(NULL, " "); + + if (result) { + x = 4; + parsed_input[x] = result; + } else { + x = 0; + char pmptstr[(strlen(cmd_entry->arg4) + 3)]; + + strcpy(pmptstr, cmd_entry->arg4); + strcat(pmptstr, ": "); + + rp_getline(pmptstr); + + if (!(strcmp(input_line, ""))) { + fprintf(fout, "? for help, q to quit.\n"); + fflush(fout); + return 0; + } + + result = strtok(input_line, " "); + + if (result) { + parsed_input[x] = result; + } else { + fprintf_flush(fout, "\n"); + return 1; + } + } + + if (strlen(parsed_input[x]) > MAXARGSZ) + parsed_input[x][MAXARGSZ] = '\0'; + + strcpy(arg4, parsed_input[x]); + p4 = arg4; } } - if (p1 && p1[0]!='?' && (cmd_entry->flags & ARG_IN4) && cmd_entry->arg4) { - if (interactive) { - if (prompt) - fprintf_flush(fout, "%s: ", cmd_entry->arg4); - if (scanfc(fin, "%s", arg4) < 1) - return -1; - p4 = arg4; - } else { - if (!argv[optind]) { - fprintf(stderr, "Invalid arg for command '%s'\n", - cmd_entry->name); - exit(1); - } - p4 = argv[optind++]; - } - } +#endif /* HAVE_LIBREADLINE */ + /* - * mutex locking needed because rigctld is multithreaded + * mutex locking needed because rotctld is multithreaded * and hamlib is not MT-safe */ #ifdef HAVE_PTHREAD @@ -531,7 +900,7 @@ void usage_rot(FILE *fout) { int i, nbspaces; - fprintf(fout, "Commands (some may not be available for this rig):\n"); + fprintf(fout, "Commands (some may not be available for this rotator):\n"); for (i = 0; test_list[i].cmd != 0; i++) { fprintf(fout, "%c: %-12s(", isprint(test_list[i].cmd) ? test_list[i].cmd : '?', test_list[i].name); @@ -548,6 +917,8 @@ void usage_rot(FILE *fout) fprintf(fout, ")\n"); } + + fprintf(fout, "\n\nPrepend long command names with '\\', e.g. '\\dump_state'\n"); } From e34a94ad6642a2e378230b0cad74342a1d8ca8ca Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Fri, 22 Feb 2013 19:09:50 -0600 Subject: [PATCH 07/10] rotctl_parse.c: Implement history recall Implement first cut at storing and recalling history. At this point history is retained for the current session only. History is stored as complete command lines even if values are entered at separate prompts. Readline allows editing and even deleting recalled history lines. --- tests/rotctl.c | 17 ++++++++++ tests/rotctl_parse.c | 79 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/tests/rotctl.c b/tests/rotctl.c index 068e2cb36..ba383d2f5 100644 --- a/tests/rotctl.c +++ b/tests/rotctl.c @@ -47,6 +47,20 @@ extern char *readline (); /* 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 */ + + #include #include "misc.h" @@ -255,6 +269,9 @@ int main (int argc, char *argv[]) #ifdef HAVE_LIBREADLINE if (interactive && prompt && have_rl) { rl_readline_name = "rigctl"; +#ifdef HAVE_READLINE_HISTORY + using_history(); /* Initialize Readline History */ +#endif } #endif /* HAVE_LIBREADLINE */ diff --git a/tests/rotctl_parse.c b/tests/rotctl_parse.c index 1fee435ba..0d5dd558f 100644 --- a/tests/rotctl_parse.c +++ b/tests/rotctl_parse.c @@ -47,6 +47,20 @@ extern char *readline (); /* 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 */ + + #include #include "serial.h" #include "misc.h" @@ -87,6 +101,11 @@ static char *input_line = (char *)NULL; static char *result = (char *)NULL; static char *parsed_input[sizeof(char) * 7]; static const int have_rl = 1; + +#ifdef HAVE_READLINE_HISTORY +static char *rp_hist_buf = (char *)NULL; +#endif + #else /* no readline */ static const int have_rl = 0; #endif @@ -510,6 +529,13 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (interactive && prompt && have_rl) { int j, x; +#ifdef HAVE_READLINE_HISTORY + /* Minimum space for 32+1+128+1+128+1+128+1+128+1+128+1+128+1 = 807 + * chars, so allocate 896 chars cleared to zero for safety. + */ + rp_hist_buf = (char *)calloc(896, sizeof(char)); +#endif + rl_instream = fin; rl_outstream = fout; @@ -564,14 +590,20 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) } /* At this point parsed_input contains the typed text of the command - * with surrounding space characters removed. + * with surrounding space characters removed. If Readline History is + * available, copy the command string into a history buffer. */ /* Single character command */ if ((strlen(parsed_input[0]) == 1) && (*parsed_input[0] != '\\')) { cmd = *parsed_input[0]; - } +#ifdef HAVE_READLINE_HISTORY + /* Store what is typed, not validated, for history. */ + if (rp_hist_buf) + strncpy(rp_hist_buf, parsed_input[0], 1); +#endif + } /* Test the command token, parsed_input[0] */ else if ((*parsed_input[0] == '\\') && (strlen(parsed_input[0]) > 1)) { char cmd_name[MAXNAMSIZ]; @@ -583,6 +615,10 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[0] + 1) >= MAXNAMSIZ) *(parsed_input[0] + MAXNAMSIZ) = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) + strncpy(rp_hist_buf, parsed_input[0], MAXNAMSIZ); +#endif /* The starting position of the source string is the first * character past the initial '\'. Using MAXNAMSIZ for the * length leaves enough space for the '\0' string terminator in the @@ -622,7 +658,7 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) return 0; } - /* \send_cmd, \send_morse */ + /* \send_cmd */ if ((cmd_entry->flags & ARG_IN_LINE) && (cmd_entry->flags & ARG_IN1) && cmd_entry->arg1) { /* Check for a non-existent delimiter so as to not break up @@ -661,6 +697,12 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg1, parsed_input[x]); p1 = arg1; } @@ -700,6 +742,12 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg1, parsed_input[x]); p1 = arg1; } @@ -737,6 +785,12 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg2, parsed_input[x]); p2 = arg2; } @@ -774,6 +828,12 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg3, parsed_input[x]); p3 = arg3; } @@ -811,9 +871,22 @@ int rotctl_parse(ROT *my_rot, FILE *fin, FILE *fout, char *argv[], int argc) if (strlen(parsed_input[x]) > MAXARGSZ) parsed_input[x][MAXARGSZ] = '\0'; +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + strncat(rp_hist_buf, " ", 1); + strncat(rp_hist_buf, parsed_input[x], MAXARGSZ); + } +#endif strcpy(arg4, parsed_input[x]); p4 = arg4; } +#ifdef HAVE_READLINE_HISTORY + if (rp_hist_buf) { + add_history(rp_hist_buf); + free(rp_hist_buf); + rp_hist_buf = (char *)NULL; + } +#endif } #endif /* HAVE_LIBREADLINE */ From 275acd2a60a2ffabff0f847e4cf76ed4edd09e26 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Fri, 22 Feb 2013 20:45:57 -0600 Subject: [PATCH 08/10] rotctl.c: Add options to save/read command history As commands from a previous session may not be desired, make reading the history file at rotctl start and writing to it at rotctl close optional by use of the -i/--read_history or -I/--save-history options. Compilation is conditional on having Readline and Readline History support detected at build system configuration time. History is stored in '$HOME/.rotctl_history' by default. An alternate path may be specified by setting the ROTCTL_HIST_DIR environment variable: $ ROTCTL_HIST_DIR=~/.rotctl rotctl -iI will temporarily set the history directory to /home/USER/.rotctl and create .rotctl_history if it does not exist. If the file cannot be read or written a warning message will be given on stderr. --- tests/rotctl.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/tests/rotctl.c b/tests/rotctl.c index ba383d2f5..22bd27f20 100644 --- a/tests/rotctl.c +++ b/tests/rotctl.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #ifdef HAVE_LIBREADLINE @@ -48,6 +49,8 @@ extern char *readline (); #endif /* HAVE_LIBREADLINE */ #ifdef HAVE_READLINE_HISTORY +# include +# define HST_SHRT_OPTS "iI" # if defined(HAVE_READLINE_HISTORY_H) # include # elif defined(HAVE_HISTORY_H) @@ -57,7 +60,9 @@ extern void add_history (); extern int write_history (); extern int read_history (); # endif /* defined(HAVE_READLINE_HISTORY_H) */ - /* no history */ +#else +/* no history */ +#define HST_SHRT_OPTS "" #endif /* HAVE_READLINE_HISTORY */ @@ -88,6 +93,10 @@ static struct option long_options[] = {"set-conf", 1, 0, 'C'}, {"show-conf",0, 0, 'L'}, {"dump-caps",0, 0, 'u'}, +#ifdef HAVE_READLINE_HISTORY + {"read-history", 0, 0, 'i'}, + {"save-history", 0, 0, 'I'}, +#endif {"verbose", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {"version", 0, 0, 'V'}, @@ -120,6 +129,14 @@ int main (int argc, char *argv[]) int verbose = 0; int show_conf = 0; int dump_caps_opt = 0; +#ifdef HAVE_READLINE_HISTORY + int rd_hist = 0; + int sv_hist = 0; + const char *hist_dir = NULL; + const char hist_file[] = "/.rotctl_history"; + char *hist_path = NULL; + struct stat hist_dir_stat; +#endif const char *rot_file=NULL; int serial_rate = 0; char conf_parms[MAXCONFLEN] = ""; @@ -128,7 +145,7 @@ int main (int argc, char *argv[]) int c; int option_index = 0; - c = getopt_long (argc, argv, SHORT_OPTIONS, + c = getopt_long (argc, argv, SHORT_OPTIONS HST_SHRT_OPTS, long_options, &option_index); if (c == -1) break; @@ -180,6 +197,14 @@ int main (int argc, char *argv[]) else send_cmd_term = optarg[0]; break; +#ifdef HAVE_READLINE_HISTORY + case 'i': + rd_hist++; + break; + case 'I': + sv_hist++; + break; +#endif case 'v': verbose++; break; @@ -268,9 +293,28 @@ int main (int argc, char *argv[]) #ifdef HAVE_LIBREADLINE if (interactive && prompt && have_rl) { - rl_readline_name = "rigctl"; + rl_readline_name = "rotctl"; #ifdef HAVE_READLINE_HISTORY using_history(); /* Initialize Readline History */ + + if (rd_hist || sv_hist) { + if (!(hist_dir = getenv("ROTCTL_HIST_DIR"))) + hist_dir = getenv("HOME"); + + if (((stat(hist_dir, &hist_dir_stat) == -1) && (errno == ENOENT)) + || !(S_ISDIR(hist_dir_stat.st_mode))) { + fprintf(stderr, "Warning: %s is not a directory!\n", hist_dir); + } + + hist_path = (char *)calloc((sizeof(char) * (strlen(hist_dir) + strlen(hist_file) + 1)), sizeof(char)); + + strncpy(hist_path, hist_dir, strlen(hist_dir)); + strncat(hist_path, hist_file, strlen(hist_file)); + } + + if (rd_hist && hist_path) + if (read_history(hist_path) == ENOENT) + fprintf(stderr, "Warning: Could not read history from %s\n", hist_path); #endif } #endif /* HAVE_LIBREADLINE */ @@ -282,6 +326,20 @@ int main (int argc, char *argv[]) } while (retcode == 0 || retcode == 2); +#ifdef HAVE_LIBREADLINE + if (interactive && prompt && have_rl) { +#ifdef HAVE_READLINE_HISTORY + if (sv_hist && hist_path) + if (write_history(hist_path) == ENOENT) + fprintf(stderr, "\nWarning: Could not write history to %s\n", hist_path); + + if ((rd_hist || sv_hist) && hist_path) { + free(hist_path); + hist_path = (char *)NULL; + } + } +#endif +#endif rot_close(my_rot); /* close port */ rot_cleanup(my_rot); /* if you care about memory */ @@ -303,6 +361,10 @@ void usage() " -L, --show-conf list all config parameters\n" " -l, --list list all model numbers and exit\n" " -u, --dump-caps dump capabilities and exit\n" +#ifdef HAVE_READLINE_HISTORY + " -i, --read-history read prior interactive session history\n" + " -I, --save-history save current interactive session history\n" +#endif " -v, --verbose set verbose mode, cumulative\n" " -h, --help display this help and exit\n" " -V, --version output version information and exit\n\n" From 7ebe65fee80b6ae9a428f475aaf5d9130793aa9b Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Fri, 22 Feb 2013 21:35:54 -0600 Subject: [PATCH 09/10] rotctl.1: Document Readline and history additions Document Readline and history additions to rotctl along with new -i/--read-history and -I/--save-history options. Document use of ROTCTL_HIST_DIR environment variable to set an alternate path for the .rotctl_history file. Other minor edits. --- NEWS | 2 ++ tests/rotctl.1 | 86 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 751ffb948..cf0cad22c 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,8 @@ Version 3.0 * WinRadio G313 updates. TNX Julian Campbel * Readline editing and history support added to rigctl interactive mode. Implement options for reading and writing history file. + * Readline editing and history support added to rotctl interactive + mode. Implement options for reading and writing history file. Version 1.2.15.3 2012-11-01 diff --git a/tests/rotctl.1 b/tests/rotctl.1 index 0a6d213b2..e915aea92 100644 --- a/tests/rotctl.1 +++ b/tests/rotctl.1 @@ -2,7 +2,7 @@ .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) -.TH ROTCTL "1" "March 1, 2012" "Hamlib" "Rotator Control Program" +.TH ROTCTL "1" "February 22, 2013" "Hamlib" "Rotator Control Program" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -29,23 +29,23 @@ interactive mode if none are provided on the command line. .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. Keep in mind that \fBHamlib\fP is BETA level software. -While a lot of backend libraries lack complete rig support, the basic functions +While a lot of backend libraries lack complete rotator support, the basic functions are usually well supported. The API may change without publicized notice, -while an advancement of the minor version (e.g. 1.1.x to 1.2.x) indicates such +while an advancement of the minor version (e.g. 1.x to 3.x) indicates such a change. .PP Please report bugs and provide feedback at the e-mail address given in the REPORTING BUGS section. Patches and code enhancements are also welcome. .SH OPTIONS This program follows the usual GNU command line syntax, with long -options starting with two dashes (`-'). +options starting with two dashes ('-'). -Here is s summary of the supported options: +Here is a summary of the supported options: .TP .B \-m, --model=id Select rotator model number. See model list (use 'rotctl -l'). .sp -NB: \fBrotctl\fP (or third party software) will use rig model 2 +NB: \fBrotctl\fP (or third party software) will use rotator model 2 for NET rotctl (rotctld). .TP .B \-r, --rot-file=device @@ -86,6 +86,26 @@ Shift-PageDown, or using the scrollbars of a virtual terminal in X or the cmd window in Windows. The output can be piped to 'more' or 'less', e.g. 'rotctl -l | more'. .TP +.B \-i, --read-history +Read previously saved command and argument history from a file +(default '~/.rotctl_history') for the current session. Available when +\fBrotctl\fP is built with Readline support (see READLINE below). +.sp +\fBN.B.\fP To read a history file stored in another directory, set the +ROTCTL_HIST_DIR environment variable, e.g. 'ROTCTL_HIST_DIR=~/tmp rotctl -i'. +When ROTCTL_HIST_DIR is not set, the value of HOME is used. +.TP +.B \-I, --save-history +Write current session and previous session(s), if -i option is given, command and +argument history to a file (default '~/.rotctl_history') at the end of the current +session. Complete commands with arguments are saved as a single line to be +recalled and used or edited. Available when \fBrotctl\fP is built with Readline +support (see READLINE below). +.sp +\fBN.B.\fP To write a history file in another directory, set the ROTCTL_HIST_DIR +environment variable, e.g. 'ROTCTL_HIST_DIR=~/tmp rotctl -I'. When ROTCTL_HIST_DIR +is not set, the value of HOME is used. +.TP .B \-v, --verbose Set verbose mode, cumulative (see DIAGNOSTICS below). .TP @@ -106,7 +126,10 @@ the operation will fail with a \fBHamlib\fP error code. Commands can be entered either as a single char, or as a long command name. Basically, the commands do not take a dash in front of them, as the options do. They may be typed in when in interactive mode -or provided as argument(s) in command line interface mode. +or provided as argument(s) in command line interface mode. In interactive +mode commands and their arguments may be entered on a single line: +.sp +Rotator command: P 123 45 .PP Since most of the \fBHamlib\fP operations have a \fIset\fP and a \fIget\fP method, an upper case letter will be used for \fIset\fP method whereas the @@ -188,8 +211,8 @@ Both are floating point values. The precision of the returned square is controlled by 'Loc Len' which should be an even numbered integer value between 2 and 12. .sp -For example, "+L -170.000000 -85.000000 12\\n" returns -"Locator: AA55AA00AA00\\n". +For example, "L -170.000000 -85.000000 12" returns +"Locator: AA55AA00AA00". .TP .B l, loc2lonlat 'Locator' Returns 'Longitude' and 'Latitude' in decimal degrees at the approximate @@ -198,8 +221,8 @@ variables internally, some rounding error occurs). West longitude is expressed as a negative value. South latitude is expressed as a negative value. Locator can be from 2 to 12 characters in length. .sp -For example, "+l AA55AA00AA00\\n" returns "Longitude: -169.999983\\nLatitude: --84.999991\\n". +For example, "l AA55AA00AA00" returns "Longitude: -169.999983 Latitude: +-84.999991". .TP .B D, dms2dec 'Degrees' 'Minutes' 'Seconds' 'S/W' Returns 'Dec Degrees', a signed floating point value. @@ -258,6 +281,45 @@ Connect to a running \fBrotctld\fP with rotor model 2 ("NET rotctl") on the local host and specifying the TCP port, and querying the position: .sp $ rotctl -m 2 -r localhost:4533 \\get_pos +.SH READLINE +If Readline library development files are found at configure time, \fBrotctl\fP +will be conditonally built with Readline support for command and argument entry. +Readline command key bindings are at their defaults as described in the Readline +manual (\fIhttp://cnswww.cns.cwru.edu/php/chet/readline/rluserman.html\fP) +although \fBrotctl\fP sets the name 'rotctl' which can be used in Conditional +Init Constructs in the Readline Init File ('~/.inputrc' by default) for custom +keybindings unique to \fBrotctl\fP. + +Command history is available with Readline support as described in the Readline +History manual +(\fIhttp://cnswww.cns.cwru.edu/php/chet/readline/history.html#SEC1\fP). Command +and argument strings are stored as single lines even when arguments are prompted +for input individually. Commands and arguments are not validated and are stored +as typed with values separated by a single space. + +Normally session history is not saved, however, use of either of the +\fI-i/--read-history\fP or \fI-I/--save-history\fP options when starting +\fBrotctl\fP will cause any previously saved history to be read in and/or the +current and any previous session history (assuming the -i and -I options are +given together) will be written out when \fBrotctl\fP is closed. Each option is +mutually exclusive, i.e. either may be given separately or in combination. This +is useful to save a set of commands and then read them later but not write the +modified history for a consistent set of test commands in interactive mode, for +example. + +History is stored in '~/.rotctl_history' by default although the destination +directory may be changed by setting the ROTCTL_HIST_DIR environment variable. +When ROTCTL_HIST_DIR is unset, the value of the HOME environment variable is +used instead. Only the destination directory may be changed at this time. + +If Readline support is not found at configure time the original internal command +handler is used. Readline is not used for \fBrotctl\fP commands entered on the +command line regardless if Readline support is built in or not. + +\fBN.B.\fP Readline support is not included in the Windows 32 binary builds +supplied by the Hamlib Project. Running \fBrotctl\fP on the Windows 32 platform +in the 'cmd' shell does give session command line history, however, it is not +saved to disk between sessions. .SH DIAGNOSTICS The \fB-v\fP, \fB--version\fP option allows different levels of diagnostics to be output to \fBstderr\fP and correspond to -v for BUG, -vv for ERR, @@ -289,7 +351,7 @@ Written by Stephane Fillod, Nate Bargmann, and the Hamlib Group .SH COPYRIGHT Copyright \(co 2000-2011 Stephane Fillod .br -Copyright \(co 2011-2012 Nate Bargmann +Copyright \(co 2011-2013 Nate Bargmann .br Copyright \(co 2000-2010 the Hamlib Group .PP From 6cf4f62acbd0ef85b35fb7128b7578f2163abb72 Mon Sep 17 00:00:00 2001 From: Nate Bargmann Date: Fri, 22 Feb 2013 21:43:14 -0600 Subject: [PATCH 10/10] Add Readline option to README files --- INSTALL | 1 + README.betatester | 4 ++-- README.developer | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index 772122781..92ba0d3fe 100644 --- a/INSTALL +++ b/INSTALL @@ -251,6 +251,7 @@ enabled with options beginning with '--with-'. At this time these options are: --with-xml-support build rigmem with XML support [default=no] + --without-readline disable readline in rigctl/rotctl [default=yes] --without-cxx-binding do not build C++ binding and demo [default=yes] --with-perl-binding build perl binding and demo [default=no] --with-perl-inc directory containing perl includes diff --git a/README.betatester b/README.betatester index 5f4a4d926..ac1ec0205 100644 --- a/README.betatester +++ b/README.betatester @@ -1,6 +1,6 @@ Hamlib - (C) Frank Singleton 2000 (vk3fcs@ix.netcom.com) (C) Stephane Fillod 2000-2011 - (C) The Hamlib Group 2000-2012 + (C) The Hamlib Group 2000-2013 Why does Hamlib need beta-testers? ================================== @@ -86,6 +86,7 @@ Optional, but highly recommended for a complete build: * libxml2 devel # xml2-config --version * libgd2 devel # gdlib-config --version * libusb devel # libusb-config --version (not 1.0.0!) +* libreadline devel # ver 5.2 or newer * Git for connection to hamlib.git.sourceforge.net N.B The libusb package is required for building most of the 'kit' backend. @@ -315,4 +316,3 @@ Needless to say, patches are also very welcome (see README.developer). :-) Stephane - F8CFE and The Hamlib Group - diff --git a/README.developer b/README.developer index fb59b82e8..705c7c566 100644 --- a/README.developer +++ b/README.developer @@ -252,6 +252,7 @@ Optional, but highly recommended: * libxml2 devel # xml2-config --version * libgd2 devel # gdlib-config --version * libusb devel # libusb-config --version (not 1.0.0!) +* libreadline devel # ver 5.2 or newer N.B.: The libusb package is required for building most of the 'kit' backend. The older version is needed, not 1.0.0 or higher. Debian and derivatives