Merge changes for readline support in nmcli (bgo #729846)

libreadline is now a build-time dependency.
We now use it throughout nmcli when asking for user input, not only in the
editor. That brings a better experience especially of 'nmcli --ask con add'
and also allows TAB completion usage.

https://bugzilla.gnome.org/show_bug.cgi?id=729846
https://bugzilla.redhat.com/show_bug.cgi?id=1007365
This commit is contained in:
Jiří Klimeš 2014-06-04 10:00:49 +02:00
commit 13b10607d4
8 changed files with 659 additions and 504 deletions

View file

@ -33,6 +33,7 @@ nmcli_SOURCES = \
nmcli_LDADD = \
$(DBUS_LIBS) \
$(GLIB_LIBS) \
$(READLINE_LIBS) \
$(top_builddir)/libnm-util/libnm-util.la \
$(top_builddir)/libnm-glib/libnm-glib.la

View file

@ -16,16 +16,20 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* (C) Copyright 2012 Red Hat, Inc.
* (C) Copyright 2012 - 2014 Red Hat, Inc.
*/
#include "config.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "common.h"
#include "utils.h"
@ -1026,3 +1030,81 @@ nmc_find_connection (GSList *list,
return found;
}
/**
* nmc_cleanup_readline:
*
* Cleanup readline when nmcli is terminated with a signal.
* It makes sure the terminal is not garbled.
*/
void
nmc_cleanup_readline (void)
{
rl_free_line_state ();
rl_cleanup_after_signal ();
}
/**
* nmc_readline:
* @prompt_fmt: prompt to print (telling user what to enter). It is standard
* printf() format string
* @...: a list of arguments according to the @prompt_fmt format string
*
* Wrapper around libreadline's readline() function.
*
* Returns: the user provided string. In case the user entered empty string,
* this function returns NULL.
*/
char *
nmc_readline (const char *prompt_fmt, ...)
{
va_list args;
char *prompt, *str;
va_start (args, prompt_fmt);
prompt = g_strdup_vprintf (prompt_fmt, args);
va_end (args);
str = readline (prompt);
/* Return NULL, not empty string */
if (str && *str == '\0') {
g_free (str);
str = NULL;
}
if (str && *str)
add_history (str);
g_free (prompt);
return str;
}
/**
* nmc_rl_gen_func_basic:
* @text: text to complete
* @state: readline state; says whether start from scratch (state == 0)
* @words: strings for completion
*
* Basic function generating list of completion strings for readline.
* See e.g. http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC49
*/
char *
nmc_rl_gen_func_basic (char *text, int state, const char **words)
{
static int list_idx, len;
const char *name;
if (!state) {
list_idx = 0;
len = strlen (text);
}
/* Return the next name which partially matches one from the 'words' list. */
while ((name = words[list_idx])) {
list_idx++;
if (strncmp (name, text, len) == 0)
return g_strdup (name);
}
return NULL;
}

View file

@ -59,4 +59,8 @@ NMConnection *nmc_find_connection (GSList *list,
const char *filter_val,
GSList **start);
void nmc_cleanup_readline (void);
char *nmc_readline (const char *prompt_fmt, ...) G_GNUC_PRINTF (1, 2);
char *nmc_rl_gen_func_basic (char *text, int state, const char **words);
#endif /* NMC_COMMON_H */

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* (C) Copyright 2010 Red Hat, Inc.
* (C) Copyright 2010 - 2014 Red Hat, Inc.
*/
#ifndef NMC_CONNECTIONS_H
@ -24,6 +24,4 @@
NMCResultCode do_connections (NmCli *nmc, int argc, char **argv);
void nmc_cleanup_readline (void);
#endif /* NMC_CONNECTIONS_H */

View file

@ -24,6 +24,7 @@
#include <stdlib.h>
#include <errno.h>
#include <netinet/ether.h>
#include <readline/readline.h>
#include <glib.h>
#include <glib/gi18n.h>
@ -65,6 +66,8 @@
#include "common.h"
#include "devices.h"
/* define some prompts */
#define PROMPT_INTERFACE _("Interface: ")
/* Available fields for 'device status' */
static NmcOutputField nmc_fields_dev_status[] = {
@ -1381,10 +1384,9 @@ do_device_connect (NmCli *nmc, int argc, char **argv)
nmc->timeout = 90;
if (argc == 0) {
if (nmc->ask) {
ifname = ifname_ask = nmc_get_user_input (_("Interface: "));
// TODO: list available devices when just Enter is pressed ?
}
if (nmc->ask)
ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE);
if (!ifname_ask) {
g_string_printf (nmc->return_text, _("Error: No interface specified."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
@ -1516,10 +1518,9 @@ do_device_disconnect (NmCli *nmc, int argc, char **argv)
nmc->timeout = 10;
if (argc == 0) {
if (nmc->ask) {
ifname = ifname_ask = nmc_get_user_input (_("Interface: "));
// TODO: list available devices when just Enter is pressed ?
}
if (nmc->ask)
ifname = ifname_ask = nmc_readline (PROMPT_INTERFACE);
if (!ifname_ask) {
g_string_printf (nmc->return_text, _("Error: No interface specified."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
@ -2022,7 +2023,7 @@ do_device_wifi_connect_network (NmCli *nmc, int argc, char **argv)
argv++;
} else {
if (nmc->ask) {
ssid_ask = nmc_get_user_input (_("SSID or BSSID: "));
ssid_ask = nmc_readline (_("SSID or BSSID: "));
param_user = ssid_ask ? ssid_ask : "";
bssid1_arr = nm_utils_hwaddr_atoba (param_user, ARPHRD_ETHER);
}
@ -2201,7 +2202,7 @@ do_device_wifi_connect_network (NmCli *nmc, int argc, char **argv)
if (ap_flags & NM_802_11_AP_FLAGS_PRIVACY) {
/* Ask for missing password when one is expected and '--ask' is used */
if (!password && nmc->ask)
password = passwd_ask = nmc_get_user_input (_("Password: "));
password = passwd_ask = nmc_readline (_("Password: "));
if (password) {
if (!connection)
@ -2560,11 +2561,82 @@ do_device_wimax (NmCli *nmc, int argc, char **argv)
}
#endif
static gboolean
is_single_word (const char* line)
{
size_t n1, n2, n3;
n1 = strspn (line, " \t");
n2 = strcspn (line+n1, " \t\0") + n1;
n3 = strspn (line+n2, " \t");
if (n3 == 0)
return TRUE;
else
return FALSE;
}
/* Global variable defined in nmcli.c */
extern NmCli nm_cli;
static char *
gen_func_ifnames (char *text, int state)
{
int i, j = 0;
const GPtrArray *devices;
const char **ifnames;
char *ret;
nm_cli.get_client (&nm_cli);
devices = nm_client_get_devices (nm_cli.client);
if (!devices || devices->len < 1)
return NULL;
ifnames = g_new (const char *, devices->len + 1);
for (i = 0; i < devices->len; i++) {
NMDevice *dev = g_ptr_array_index (devices, i);
const char *ifname = nm_device_get_iface (dev);
ifnames[j++] = ifname;
}
ifnames[j] = NULL;
ret = nmc_rl_gen_func_basic (text, state, ifnames);
g_free (ifnames);
return ret;
}
static char **
nmcli_device_tab_completion (char *text, int start, int end)
{
char **match_array = NULL;
CPFunction *generator_func = NULL;
/* Disable readline's default filename completion */
rl_attempted_completion_over = 1;
/* Disable appending space after completion */
rl_completion_append_character = '\0';
if (!is_single_word (rl_line_buffer))
return NULL;
if (g_strcmp0 (rl_prompt, PROMPT_INTERFACE) == 0)
generator_func = gen_func_ifnames;
if (generator_func)
match_array = rl_completion_matches (text, generator_func);
return match_array;
}
NMCResultCode
do_devices (NmCli *nmc, int argc, char **argv)
{
GError *error = NULL;
rl_attempted_completion_function = (CPPFunction *) nmcli_device_tab_completion;
if (argc == 0) {
if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error))
goto opt_error;

View file

@ -37,6 +37,7 @@
#include "nmcli.h"
#include "utils.h"
#include "common.h"
#include "connections.h"
#include "devices.h"
#include "network-manager.h"

View file

@ -387,6 +387,14 @@ PKG_CHECK_MODULES(UUID, uuid)
AC_SUBST(UUID_CFLAGS)
AC_SUBST(UUID_LIBS)
dnl
dnl Checks for readline library - used by nmcli
dnl
#PKG_CHECK_MODULES(READLINE, readline)
AC_CHECK_LIB(readline, readline, [READLINE_LIBS=-lreadline], [AC_MSG_ERROR(readline library is required)])
AC_CHECK_HEADERS(readline/readline.h, [], [AC_MSG_ERROR(readline/readline.h - readline devel files required)])
AC_SUBST(READLINE_LIBS)
# Intel WiMAX SDK checks
PKG_CHECK_MODULES(IWMX_SDK, [libiWmxSdk-0 >= 1.5.1], [have_wimax=yes],[have_wimax=no])
AC_ARG_ENABLE(wimax, AS_HELP_STRING([--enable-wimax], [enable WiMAX support]),