diff options
author | Jiřà Klimeš <jklimes@redhat.com> | 2014-06-04 10:00:49 +0200 |
---|---|---|
committer | Jiřà Klimeš <jklimes@redhat.com> | 2014-06-04 10:11:12 +0200 |
commit | 13b10607d43e3bc4f66f5ee7c0e1f1ad449f0ca8 (patch) | |
tree | 2285db23ab5f02446d35085060048f15ccc2b917 | |
parent | fd93fb9fb966038390053627b1ca86d65c6db399 (diff) | |
parent | 82db87a14413cdd0d288f70f9fd261a194317fa2 (diff) | |
download | NetworkManager-13b10607d43e3bc4f66f5ee7c0e1f1ad449f0ca8.tar.gz |
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
-rw-r--r-- | cli/src/Makefile.am | 1 | ||||
-rw-r--r-- | cli/src/common.c | 84 | ||||
-rw-r--r-- | cli/src/common.h | 4 | ||||
-rw-r--r-- | cli/src/connections.c | 969 | ||||
-rw-r--r-- | cli/src/connections.h | 4 | ||||
-rw-r--r-- | cli/src/devices.c | 92 | ||||
-rw-r--r-- | cli/src/nmcli.c | 1 | ||||
-rw-r--r-- | configure.ac | 8 |
8 files changed, 659 insertions, 504 deletions
diff --git a/cli/src/Makefile.am b/cli/src/Makefile.am index f4f8fd943a..e61c036898 100644 --- a/cli/src/Makefile.am +++ b/cli/src/Makefile.am @@ -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 diff --git a/cli/src/common.c b/cli/src/common.c index 1a63504eff..bbefc4bbe8 100644 --- a/cli/src/common.c +++ b/cli/src/common.c @@ -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; +} + diff --git a/cli/src/common.h b/cli/src/common.h index 3b5f9986dc..aafd4491a2 100644 --- a/cli/src/common.h +++ b/cli/src/common.h @@ -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 */ diff --git a/cli/src/connections.c b/cli/src/connections.c index 1129755b37..c777ddd771 100644 --- a/cli/src/connections.c +++ b/cli/src/connections.c @@ -30,6 +30,8 @@ #include <errno.h> #include <signal.h> #include <netinet/ether.h> +#include <readline/readline.h> +#include <readline/history.h> #include <nm-client.h> #include <nm-device-ethernet.h> @@ -63,6 +65,18 @@ #define EDITOR_PROMPT_PROPERTY _("Property name? ") #define EDITOR_PROMPT_CON_TYPE _("Enter connection type: ") +/* define some other prompts */ +#define PROMPT_CON_TYPE _("Connection type: ") +#define PROMPT_VPN_TYPE _("VPN type: ") +#define PROMPT_BOND_MASTER _("Bond master: ") +#define PROMPT_TEAM_MASTER _("Team master: ") +#define PROMPT_BRIDGE_MASTER _("Bridge master: ") +#define PROMPT_CONNECTION _("Connection (name, UUID, or path): ") + +static const char *nmc_known_vpns[] = + { "openvpn", "vpnc", "pptp", "openconnect", "openswan", "libreswan", + "ssh", "l2tp", "iodine", NULL }; + /* Available fields for 'connection show' */ static NmcOutputField nmc_fields_con_show[] = { {"NAME", N_("NAME"), 25}, /* 0 */ @@ -245,7 +259,7 @@ extern GMainLoop *loop; static ArgsInfo args_info; static guint progress_id = 0; /* ID of event source for displaying progress */ -/* for readline TAB completion */ +/* for readline TAB completion in editor */ typedef struct { NmCli *nmc; char *con_type; @@ -254,6 +268,11 @@ typedef struct { } TabCompletionInfo; static TabCompletionInfo nmc_tab_completion = {NULL, NULL, NULL, NULL}; +/* Global variable defined in nmcli.c - used for TAB completion */ +extern NmCli nm_cli; + +static char *gen_connection_types (char *text, int state); + static void usage (void) { @@ -1945,10 +1964,8 @@ do_connection_up (NmCli *nmc, int argc, char **argv) if (argc == 0) { if (nmc->ask) { - line = nmc_get_user_input (_("Connection (name, UUID, or path): ")); + line = nmc_readline (PROMPT_CONNECTION); name = line ? line : ""; - // TODO: enhancement: when just Enter is pressed (line is NULL), list - // available connections so that the user can select one } } else if (strcmp (*argv, "ifname") != 0) { if ( strcmp (*argv, "id") == 0 @@ -2057,7 +2074,7 @@ do_connection_down (NmCli *nmc, int argc, char **argv) if (argc == 0) { if (nmc->ask) { - line = nmc_get_user_input (_("Connection (name, UUID, or path): ")); + line = nmc_readline (PROMPT_CONNECTION); nmc_string_to_arg_array (line, "", &arg_arr, &arg_num); arg_ptr = arg_arr; } @@ -2562,18 +2579,29 @@ check_infiniband_p_key (const char *p_key, guint32 *p_key_int, GError **error) return TRUE; } +/* Checks InfiniBand mode. + * It accepts shortcuts and normalizes them ('mode' argument is modified on success). + */ static gboolean -check_infiniband_mode (const char *mode, GError **error) +check_infiniband_mode (char *mode, GError **error) { + char *tmp; + const char *checked_mode; + const char *modes[] = { "datagram", "connected", NULL }; + if (!mode) return TRUE; - if (strcmp (mode, "datagram") && strcmp (mode, "connected")) { + tmp = g_strstrip (g_strdup (mode)); + checked_mode = nmc_string_is_valid (tmp, modes, NULL); + g_free (tmp); + if (checked_mode) { + g_free (mode); + mode = g_strdup (checked_mode); + } else g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'mode': '%s' is not a valid InfiniBand transport mode [datagram, connected]."), mode); - return FALSE; - } - return TRUE; + return !!checked_mode; } static gboolean @@ -2808,26 +2836,79 @@ bridge_prop_string_to_uint (const char *str, return TRUE; } +#define WORD_YES "yes" +#define WORD_NO "no" +#define WORD_LOC_YES _("yes") +#define WORD_LOC_NO _("no") +static const char * +prompt_yes_no (gboolean default_yes, char *delim) +{ + static char prompt[128] = { 0 }; + + if (!delim) + delim = ""; + + snprintf (prompt, sizeof (prompt), "(%s/%s) [%s]%s ", + WORD_LOC_YES, WORD_LOC_NO, + default_yes ? WORD_LOC_YES : WORD_LOC_NO, delim); + + return prompt; +} + static void -do_questionnaire_ethernet (gboolean ethernet, char **mtu, char **mac, char **cloned_mac) +normalize_yes_no (char *yes_no) +{ + const char *tmp; + const char *strv[] = { WORD_LOC_YES, WORD_LOC_NO, NULL }; + + if (!yes_no) + return; + + g_strstrip (yes_no); + tmp = nmc_string_is_valid (yes_no, strv, NULL); + if (g_strcmp0 (tmp, WORD_LOC_YES) == 0) { + g_free (yes_no); + yes_no = g_strdup (WORD_YES); + } else if (g_strcmp0 (tmp, WORD_LOC_NO) == 0) { + g_free (yes_no); + yes_no = g_strdup (WORD_NO); + } +} + +static gboolean +want_provide_opt_args (const char *type, int num) { char *answer; - gboolean answer_bool; + gboolean ret = TRUE; + + /* Ask for optional arguments. */ + printf (ngettext ("There is %d optional argument for '%s' connection type.\n", + "There are %d optional arguments for '%s' connection type.\n", num), + num, type); + answer = nmc_readline (ngettext ("Do you want to provide it? %s", + "Do you want to provide them? %s", num), + prompt_yes_no (TRUE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (answer && matches (answer, WORD_LOC_YES) != 0) + ret = FALSE; + g_free (answer); + return ret; +} + +static void +do_questionnaire_ethernet (gboolean ethernet, char **mtu, char **mac, char **cloned_mac) +{ gboolean once_more; GError *error = NULL; const char *type = ethernet ? _("ethernet") : _("Wi-Fi"); /* Ask for optional arguments */ - printf (_("There are 3 optional arguments for '%s' connection type.\n"), type);; - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (type, 3)) return; - } if (!*mtu) { do { - *mtu = nmc_get_user_input (_("MTU [auto]: ")); + *mtu = nmc_readline (_("MTU [auto]: ")); once_more = !check_and_convert_mtu (*mtu, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -2838,7 +2919,7 @@ do_questionnaire_ethernet (gboolean ethernet, char **mtu, char **mac, char **clo } if (!*mac) { do { - *mac = nmc_get_user_input (_("MAC [none]: ")); + *mac = nmc_readline (_("MAC [none]: ")); once_more = !check_and_convert_mac (*mac, NULL, ARPHRD_ETHER, "mac", &error); if (once_more) { printf ("%s\n", error->message); @@ -2849,7 +2930,7 @@ do_questionnaire_ethernet (gboolean ethernet, char **mtu, char **mac, char **clo } if (!*cloned_mac) { do { - *cloned_mac = nmc_get_user_input (_("Cloned MAC [none]: ")); + *cloned_mac = nmc_readline (_("Cloned MAC [none]: ")); once_more = !check_and_convert_mac (*cloned_mac, NULL, ARPHRD_ETHER, "cloned-mac", &error); if (once_more) { printf ("%s\n", error->message); @@ -2858,30 +2939,24 @@ do_questionnaire_ethernet (gboolean ethernet, char **mtu, char **mac, char **clo } } while (once_more); } - - g_free (answer); - return; } +#define WORD_DATAGRAM "datagram" +#define WORD_CONNECTED "connected" +#define PROMPT_IB_MODE "(" WORD_DATAGRAM "/" WORD_CONNECTED ") [" WORD_DATAGRAM "]: " static void do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, char **p_key) { - char *answer; - gboolean answer_bool; gboolean once_more; GError *error = NULL; /* Ask for optional arguments */ - printf (_("There are 5 optional arguments for 'InfiniBand' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("InfiniBand"), 5)) return; - } if (!*mtu) { do { - *mtu = nmc_get_user_input (_("MTU [auto]: ")); + *mtu = nmc_readline (_("MTU [auto]: ")); once_more = !check_and_convert_mtu (*mtu, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -2892,7 +2967,7 @@ do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, } if (!*mac) { do { - *mac = nmc_get_user_input (_("MAC [none]: ")); + *mac = nmc_readline (_("MAC [none]: ")); once_more = !check_and_convert_mac (*mac, NULL, ARPHRD_INFINIBAND, "mac", &error); if (once_more) { printf ("%s\n", error->message); @@ -2903,7 +2978,7 @@ do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, } if (!*mode) { do { - *mode = nmc_get_user_input (_("Transport mode (datagram or connected) [datagram]: ")); + *mode = nmc_readline (_("Transport mode %s"), PROMPT_IB_MODE); if (!*mode) *mode = g_strdup ("datagram"); once_more = !check_infiniband_mode (*mode, &error); @@ -2916,7 +2991,7 @@ do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, } if (!*parent) { do { - *parent = nmc_get_user_input (_("Parent interface [none]: ")); + *parent = nmc_readline (_("Parent interface [none]: ")); once_more = !check_infiniband_parent (*parent, &error); if (once_more) { printf ("%s\n", error->message); @@ -2927,7 +3002,7 @@ do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, } if (!*p_key) { do { - *p_key = nmc_get_user_input (_("P_KEY [none]: ")); + *p_key = nmc_readline (_("P_KEY [none]: ")); once_more = !check_infiniband_p_key (*p_key, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -2941,9 +3016,6 @@ do_questionnaire_infiniband (char **mtu, char **mac, char **mode, char **parent, } } while (once_more); } - - g_free (answer); - return; } static void @@ -2956,22 +3028,16 @@ do_questionnaire_wifi (char **mtu, char **mac, char **cloned_mac) static void do_questionnaire_wimax (char **mac) { - char *answer; - gboolean answer_bool; gboolean once_more; GError *error = NULL; /* Ask for optional 'wimax' arguments. */ - printf (_("There is 1 optional argument for 'WiMax' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide it? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("WiMAX"), 1)) return; - } if (!*mac) { do { - *mac = nmc_get_user_input (_("MAC [none]: ")); + *mac = nmc_readline (_("MAC [none]: ")); once_more = !check_and_convert_mac (*mac, NULL, ARPHRD_ETHER, "mac", &error); if (once_more) { printf ("%s\n", error->message); @@ -2980,35 +3046,26 @@ do_questionnaire_wimax (char **mac) } } while (once_more); } - - g_free (answer); - return; } static void do_questionnaire_pppoe (char **password, char **service, char **mtu, char **mac) { - char *answer; - gboolean answer_bool; gboolean once_more; GError *error = NULL; /* Ask for optional 'pppoe' arguments. */ - printf (_("There are 4 optional arguments for 'PPPoE' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("PPPoE"), 4)) return; - } if (!*password) - *password = nmc_get_user_input (_("Password [none]: ")); + *password = nmc_readline (_("Password [none]: ")); if (!*service) - *service = nmc_get_user_input (_("Service [none]: ")); + *service = nmc_readline (_("Service [none]: ")); if (!*mtu) { do { - *mtu = nmc_get_user_input (_("MTU [auto]: ")); + *mtu = nmc_readline (_("MTU [auto]: ")); once_more = !check_and_convert_mtu (*mtu, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -3019,7 +3076,7 @@ do_questionnaire_pppoe (char **password, char **service, char **mtu, char **mac) } if (!*mac) { do { - *mac = nmc_get_user_input (_("MAC [none]: ")); + *mac = nmc_readline (_("MAC [none]: ")); once_more = !check_and_convert_mac (*mac, NULL, ARPHRD_ETHER, "mac", &error); if (once_more) { printf ("%s\n", error->message); @@ -3028,88 +3085,66 @@ do_questionnaire_pppoe (char **password, char **service, char **mtu, char **mac) } } while (once_more); } - - g_free (answer); - return; } static void do_questionnaire_mobile (char **user, char **password) { - char *answer; - gboolean answer_bool; - /* Ask for optional 'gsm' or 'cdma' arguments. */ - printf (_("There are 2 optional arguments for 'mobile broadband' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("mobile broadband"), 2)) return; - } if (!*user) - *user = nmc_get_user_input (_("Username [none]: ")); + *user = nmc_readline (_("Username [none]: ")); if (!*password) - *password = nmc_get_user_input (_("Password [none]: ")); - - g_free (answer); - return; + *password = nmc_readline (_("Password [none]: ")); } +#define WORD_PANU "panu" +#define WORD_DUN_GSM "dun-gsm" +#define WORD_DUN_CDMA "dun-cdma" +#define PROMPT_BT_TYPE "(" WORD_PANU "/" WORD_DUN_GSM "/" WORD_DUN_CDMA ") [" WORD_PANU "]: " static void do_questionnaire_bluetooth (char **bt_type) { - char *answer; - gboolean answer_bool; gboolean once_more; /* Ask for optional 'bluetooth' arguments. */ - printf (_("There is 1 optional argument for 'bluetooth' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide it? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("bluetooth"), 1)) return; - } if (!*bt_type) { + const char *types[] = { "dun", "dun-gsm", "dun-cdma", "panu", NULL }; + const char *tmp; do { - *bt_type = nmc_get_user_input (_("Bluetooth type (panu, dun-gsm or dun-cdma) [panu]: ")); + *bt_type = nmc_readline (_("Bluetooth type %s"), PROMPT_BT_TYPE); if (!*bt_type) *bt_type = g_strdup ("panu"); - once_more = strcmp (*bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN) - && strcmp (*bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN"-gsm") - && strcmp (*bt_type, NM_SETTING_BLUETOOTH_TYPE_DUN"-cdma") - && strcmp (*bt_type, NM_SETTING_BLUETOOTH_TYPE_PANU); + tmp = nmc_string_is_valid (*bt_type, types, NULL); + once_more = !tmp; if (once_more) { printf (_("Error: 'bt-type': '%s' is not a valid bluetooth type.\n"), *bt_type); g_free (*bt_type); } } while (once_more); + g_free (*bt_type); + *bt_type = g_strdup (tmp); } - - g_free (answer); - return; } static void do_questionnaire_vlan (char **mtu, char **flags, char **ingress, char **egress) { - char *answer; - gboolean answer_bool; gboolean once_more; GError *error = NULL; /* Ask for optional 'vlan' arguments. */ - printf (_("There are 4 optional arguments for 'VLAN' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("VLAN"), 4)) return; - } if (!*mtu) { do { - *mtu = nmc_get_user_input (_("MTU [auto]: ")); + *mtu = nmc_readline (_("MTU [auto]: ")); once_more = !check_and_convert_mtu (*mtu, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -3120,7 +3155,7 @@ do_questionnaire_vlan (char **mtu, char **flags, char **ingress, char **egress) } if (!*flags) { do { - *flags = nmc_get_user_input (_("VLAN flags (<0-7>) [none]: ")); + *flags = nmc_readline (_("VLAN flags (<0-7>) [none]: ")); once_more = !check_and_convert_vlan_flags (*flags, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -3131,7 +3166,7 @@ do_questionnaire_vlan (char **mtu, char **flags, char **ingress, char **egress) } if (!*ingress) { do { - *ingress = nmc_get_user_input (_("Ingress priority maps [none]: ")); + *ingress = nmc_readline (_("Ingress priority maps [none]: ")); once_more = !check_and_convert_vlan_prio_maps (*ingress, NM_VLAN_INGRESS_MAP, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -3142,7 +3177,7 @@ do_questionnaire_vlan (char **mtu, char **flags, char **ingress, char **egress) } if (!*egress) { do { - *egress = nmc_get_user_input (_("Egress priority maps [none]: ")); + *egress = nmc_readline (_("Egress priority maps [none]: ")); once_more = !check_and_convert_vlan_prio_maps (*egress, NM_VLAN_EGRESS_MAP, NULL, &error); if (once_more) { printf ("%s\n", error->message); @@ -3151,34 +3186,30 @@ do_questionnaire_vlan (char **mtu, char **flags, char **ingress, char **egress) } } while (once_more); } - - g_free (answer); - return; } +#define PROMPT_BOND_MODE _("Bonding mode [balance-rr]: ") +#define WORD_MIIMON "miimon" +#define WORD_ARP "arp" +#define PROMPT_BOND_MON_MODE "(" WORD_MIIMON "/" WORD_ARP ") [" WORD_MIIMON "]: " static void do_questionnaire_bond (char **mode, char **primary, char **miimon, char **downdelay, char **updelay, char **arpinterval, char **arpiptarget) { - char *answer, *monitor_mode; - gboolean answer_bool; + char *monitor_mode; unsigned long tmp; gboolean once_more; GError *error = NULL; /* Ask for optional 'bond' arguments. */ - printf (_("There are optional arguments for 'bond' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("bond"), 7)) return; - } if (!*mode) { const char *mode_tmp; do { - *mode = nmc_get_user_input (_("Bonding mode [balance-rr]: ")); + *mode = nmc_readline (PROMPT_BOND_MODE); if (!*mode) *mode = g_strdup ("balance-rr"); mode_tmp = nmc_bond_validate_mode (*mode, &error); @@ -3194,7 +3225,7 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon, if (g_strcmp0 (*mode, "active-backup") == 0 && !*primary) { do { - *primary = nmc_get_user_input (_("Bonding primary interface [none]: ")); + *primary = nmc_readline (_("Bonding primary interface [none]: ")); once_more = *primary && !nm_utils_iface_valid_name (*primary); if (once_more) { printf (_("Error: 'primary': '%s' is not a valid interface name.\n"), @@ -3205,21 +3236,22 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon, } do { - monitor_mode = nmc_get_user_input (_("Bonding monitoring mode (miimon or arp) [miimon]: ")); + monitor_mode = nmc_readline (_("Bonding monitoring mode %s"), PROMPT_BOND_MON_MODE); if (!monitor_mode) - monitor_mode = g_strdup ("miimon"); - once_more = strcmp (monitor_mode, "miimon") && strcmp (monitor_mode, "arp"); + monitor_mode = g_strdup (WORD_MIIMON); + g_strstrip (monitor_mode); + once_more = matches (monitor_mode, WORD_MIIMON) != 0 && matches (monitor_mode, WORD_ARP) != 0; if (once_more) { printf (_("Error: '%s' is not a valid monitoring mode; use '%s' or '%s'.\n"), - monitor_mode, "miimon", "arp"); + monitor_mode, WORD_MIIMON, WORD_ARP); g_free (monitor_mode); } } while (once_more); - if (strcmp (monitor_mode, "miimon") == 0) { + if (matches (monitor_mode, WORD_MIIMON) == 0) { if (!*miimon) { do { - *miimon = nmc_get_user_input (_("Bonding miimon [100]: ")); + *miimon = nmc_readline (_("Bonding miimon [100]: ")); once_more = *miimon && !nmc_string_to_uint (*miimon, TRUE, 0, G_MAXUINT32, &tmp); if (once_more) { printf (_("Error: 'miimon': '%s' is not a valid number <0-%u>.\n"), @@ -3230,7 +3262,7 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon, } if (!*downdelay) { do { - *downdelay = nmc_get_user_input (_("Bonding downdelay [0]: ")); + *downdelay = nmc_readline (_("Bonding downdelay [0]: ")); once_more = *downdelay && !nmc_string_to_uint (*downdelay, TRUE, 0, G_MAXUINT32, &tmp); if (once_more) { printf (_("Error: 'downdelay': '%s' is not a valid number <0-%u>.\n"), @@ -3241,7 +3273,7 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon, } if (!*updelay) { do { - *updelay = nmc_get_user_input (_("Bonding updelay [0]: ")); + *updelay = nmc_readline (_("Bonding updelay [0]: ")); once_more = *updelay && !nmc_string_to_uint (*updelay, TRUE, 0, G_MAXUINT32, &tmp); if (once_more) { printf (_("Error: 'updelay': '%s' is not a valid number <0-%u>.\n"), @@ -3253,7 +3285,7 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon, } else { if (!*arpinterval) { do { - *arpinterval = nmc_get_user_input (_("Bonding arp-interval [0]: ")); + *arpinterval = nmc_readline (_("Bonding arp-interval [0]: ")); once_more = *arpinterval && !nmc_string_to_uint (*arpinterval, TRUE, 0, G_MAXUINT32, &tmp); if (once_more) { printf (_("Error: 'arp-interval': '%s' is not a valid number <0-%u>.\n"), @@ -3264,35 +3296,27 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon, } if (!*arpiptarget) { //FIXME: verify the string - *arpiptarget = nmc_get_user_input (_("Bonding arp-ip-target [none]: ")); + *arpiptarget = nmc_readline (_("Bonding arp-ip-target [none]: ")); } } - g_free (answer); g_free (monitor_mode); - return; } static void do_questionnaire_team_common (const char *type_name, char **config) { - char *answer; - gboolean answer_bool; gboolean once_more; char *json = NULL; GError *error = NULL; - /* Ask for optional 'team' arguments. */ - printf (_("There is 1 optional argument for '%s' connection type.\n"), type_name); - answer = nmc_get_user_input (_("Do you want to provide it? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + /* Ask for optional arguments. */ + if (!want_provide_opt_args (type_name, 1)) return; - } if (!*config) { do { - *config = nmc_get_user_input (_("Team JSON configuration [none]: ")); + *config = nmc_readline (_("Team JSON configuration [none]: ")); once_more = !nmc_team_check_config (*config, &json, &error); if (once_more) { printf ("Error: %s\n", error->message); @@ -3303,8 +3327,6 @@ do_questionnaire_team_common (const char *type_name, char **config) } *config = json; - g_free (answer); - return; } /* Both team and team-slave curently have just ithe same one optional argument */ @@ -3324,28 +3346,23 @@ static void do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **hello_time, char **max_age, char **ageing_time, char **mac) { - char *answer; - gboolean answer_bool; unsigned long tmp; gboolean once_more; GError *error = NULL; /* Ask for optional 'bridge' arguments. */ - printf (_("There are 7 optional arguments for 'bridge' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("bridge"), 7)) return; - } if (!*stp) { gboolean stp_bool; do { - *stp = nmc_get_user_input (_("Enable STP (yes/no) [yes]: ")); + *stp = nmc_readline (_("Enable STP %s"), prompt_yes_no (TRUE, ":")); *stp = *stp ? *stp : g_strdup ("yes"); + normalize_yes_no (*stp); once_more = !nmc_string_to_bool (*stp, &stp_bool, &error); if (once_more) { - printf (_("Error: 'stp': '%s'.\n"), error->message); + printf (_("Error: 'stp': %s.\n"), error->message); g_clear_error (&error); g_free (*stp); } @@ -3353,7 +3370,7 @@ do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **h } if (!*priority) { do { - *priority = nmc_get_user_input (_("STP priority [32768]: ")); + *priority = nmc_readline (_("STP priority [32768]: ")); *priority = *priority ? *priority : g_strdup ("32768"); once_more = !nmc_string_to_uint (*priority, TRUE, 0, G_MAXUINT16, &tmp); if (once_more) { @@ -3365,7 +3382,7 @@ do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **h } if (!*fwd_delay) { do { - *fwd_delay = nmc_get_user_input (_("Forward delay [15]: ")); + *fwd_delay = nmc_readline (_("Forward delay [15]: ")); *fwd_delay = *fwd_delay ? *fwd_delay : g_strdup ("15"); once_more = !nmc_string_to_uint (*fwd_delay, TRUE, 2, 30, &tmp); if (once_more) { @@ -3378,7 +3395,7 @@ do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **h if (!*hello_time) { do { - *hello_time = nmc_get_user_input (_("Hello time [2]: ")); + *hello_time = nmc_readline (_("Hello time [2]: ")); *hello_time = *hello_time ? *hello_time : g_strdup ("2"); once_more = !nmc_string_to_uint (*hello_time, TRUE, 1, 10, &tmp); if (once_more) { @@ -3390,7 +3407,7 @@ do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **h } if (!*max_age) { do { - *max_age = nmc_get_user_input (_("Max age [20]: ")); + *max_age = nmc_readline (_("Max age [20]: ")); *max_age = *max_age ? *max_age : g_strdup ("20"); once_more = !nmc_string_to_uint (*max_age, TRUE, 6, 40, &tmp); if (once_more) { @@ -3402,7 +3419,7 @@ do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **h } if (!*ageing_time) { do { - *ageing_time = nmc_get_user_input (_("MAC address ageing time [300]: ")); + *ageing_time = nmc_readline (_("MAC address ageing time [300]: ")); *ageing_time = *ageing_time ? *ageing_time : g_strdup ("300"); once_more = !nmc_string_to_uint (*ageing_time, TRUE, 0, 1000000, &tmp); if (once_more) { @@ -3423,31 +3440,22 @@ do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, char **h } } while (once_more); } - - g_free (answer); - return; } static void do_questionnaire_bridge_slave (char **priority, char **path_cost, char **hairpin) { - char *answer; - gboolean answer_bool; unsigned long tmp; gboolean once_more; GError *error = NULL; /* Ask for optional 'bridge-slave' arguments. */ - printf (_("There are 3 optional arguments for 'bridge-slave' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("bridge-slave"), 3)) return; - } if (!*priority) { do { - *priority = nmc_get_user_input (_("Bridge port priority [32]: ")); + *priority = nmc_readline (_("Bridge port priority [32]: ")); *priority = *priority ? *priority : g_strdup ("32"); once_more = !bridge_prop_string_to_uint (*priority, "priority", NM_TYPE_SETTING_BRIDGE_PORT, NM_SETTING_BRIDGE_PORT_PRIORITY, &tmp, &error); @@ -3460,7 +3468,7 @@ do_questionnaire_bridge_slave (char **priority, char **path_cost, char **hairpin } if (!*path_cost) { do { - *path_cost = nmc_get_user_input (_("Bridge port STP path cost [100]: ")); + *path_cost = nmc_readline (_("Bridge port STP path cost [100]: ")); *path_cost = *path_cost ? *path_cost : g_strdup ("100"); once_more = !bridge_prop_string_to_uint (*path_cost, "path-cost", NM_TYPE_SETTING_BRIDGE_PORT, NM_SETTING_BRIDGE_PORT_PATH_COST, &tmp, &error); @@ -3474,62 +3482,44 @@ do_questionnaire_bridge_slave (char **priority, char **path_cost, char **hairpin if (!*hairpin) { gboolean hairpin_bool; do { - *hairpin = nmc_get_user_input (_("Hairpin (yes/no) [yes]: ")); + *hairpin = nmc_readline (_("Hairpin %s"), prompt_yes_no (TRUE, ":")); *hairpin = *hairpin ? *hairpin : g_strdup ("yes"); + normalize_yes_no (*hairpin); once_more = !nmc_string_to_bool (*hairpin, &hairpin_bool, &error); if (once_more) { - printf (_("Error: 'hairpin': '%s'.\n"), error->message); + printf (_("Error: 'hairpin': %s.\n"), error->message); g_clear_error (&error); g_free (*hairpin); } } while (once_more); } - - g_free (answer); - return; } static void do_questionnaire_vpn (char **user) { - char *answer; - gboolean answer_bool; - /* Ask for optional 'vpn' arguments. */ - printf (_("There is 1 optional argument for 'VPN' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide it? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("VPN"), 1)) return; - } if (!*user) - *user = nmc_get_user_input (_("Username [none]: ")); - - g_free (answer); - return; + *user = nmc_readline (_("Username [none]: ")); } static void do_questionnaire_olpc (char **channel, char **dhcp_anycast) { - char *answer; - gboolean answer_bool; unsigned long tmp; gboolean once_more; GError *error = NULL; /* Ask for optional 'olpc' arguments. */ - printf (_("There are 2 optional arguments for 'OLPC Mesh' connection type.\n")); - answer = nmc_get_user_input (_("Do you want to provide them? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { - g_free (answer); + if (!want_provide_opt_args (_("OLPC Mesh"), 2)) return; - } if (!*channel) { do { - *channel = nmc_get_user_input (_("OLPC Mesh channel [1]: ")); + *channel = nmc_readline (_("OLPC Mesh channel [1]: ")); once_more = *channel && !nmc_string_to_uint (*channel, TRUE, 1, 13, &tmp); if (once_more) { printf (_("Error: 'channel': '%s' is not a valid number <1-13>.\n"), @@ -3540,7 +3530,7 @@ do_questionnaire_olpc (char **channel, char **dhcp_anycast) } if (!*dhcp_anycast) { do { - *dhcp_anycast = nmc_get_user_input (_("DHCP anycast MAC address [none]: ")); + *dhcp_anycast = nmc_readline (_("DHCP anycast MAC address [none]: ")); once_more = !check_and_convert_mac (*dhcp_anycast, NULL, ARPHRD_ETHER, "dhcp-anycast", &error); if (once_more) { printf ("%s\n", error->message); @@ -3549,9 +3539,6 @@ do_questionnaire_olpc (char **channel, char **dhcp_anycast) } } while (once_more); } - - g_free (answer); - return; } static gboolean @@ -3594,7 +3581,7 @@ ask_for_ip_addresses (NMConnection *connection, int family) ip_loop = TRUE; do { - str = nmc_get_user_input (prompt); + str = nmc_readline ("%s", prompt); split_address (str, &ip, &gw, &rest); if (ip) { if (family == 4) @@ -3629,11 +3616,11 @@ static void do_questionnaire_ip (NMConnection *connection) { char *answer; - gboolean answer_bool; /* Ask for IP addresses */ - answer = nmc_get_user_input (_("Do you want to add IP addresses? (yes/no) [yes] ")); - if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) { + answer = nmc_readline (_("Do you want to add IP addresses? %s"), prompt_yes_no (TRUE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (answer && matches (answer, WORD_LOC_YES) != 0) { g_free (answer); return; } @@ -3840,7 +3827,7 @@ cleanup_ib: return FALSE; if (!ssid && ask) - ssid = ssid_ask = nmc_get_user_input (_("SSID: ")); + ssid = ssid_ask = nmc_readline (_("SSID: ")); if (!ssid) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'ssid' is required.")); @@ -3907,7 +3894,7 @@ cleanup_wifi: return FALSE; if (!nsp_name && ask) - nsp_name = nsp_name_ask = nmc_get_user_input (_("WiMAX NSP name: ")); + nsp_name = nsp_name_ask = nmc_readline (_("WiMAX NSP name: ")); if (!nsp_name) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'nsp' is required.")); @@ -3965,7 +3952,7 @@ cleanup_wimax: return FALSE; if (!username && ask) - username = username_ask = nmc_get_user_input (_("PPPoE username: ")); + username = username_ask = nmc_readline (_("PPPoE username: ")); if (!username) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'username' is required.")); @@ -4039,7 +4026,7 @@ cleanup_pppoe: return FALSE; if (!apn && ask && is_gsm) - apn = apn_ask = nmc_get_user_input (_("APN: ")); + apn = apn_ask = nmc_readline (_("APN: ")); if (!apn && is_gsm) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'apn' is required.")); @@ -4101,7 +4088,7 @@ cleanup_mobile: return FALSE; if (!addr && ask) - addr = addr_ask = nmc_get_user_input (_("Bluetooth device address: ")); + addr = addr_ask = nmc_readline (_("Bluetooth device address: ")); if (!addr) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'addr' is required.")); @@ -4192,14 +4179,14 @@ cleanup_bt: return FALSE; if (!parent && ask) - parent = parent_ask = nmc_get_user_input (_("VLAN parent device or connection UUID: ")); + parent = parent_ask = nmc_readline (_("VLAN parent device or connection UUID: ")); if (!parent) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'dev' is required.")); return FALSE; } if (!vlan_id && ask) - vlan_id = vlan_id_ask = nmc_get_user_input (_("VLAN ID <0-4095>: ")); + vlan_id = vlan_id_ask = nmc_readline (_("VLAN ID <0-4095>: ")); if (!vlan_id) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'id' is required.")); @@ -4402,11 +4389,14 @@ cleanup_bond: {"type", TRUE, &type, FALSE}, {NULL} }; + /* Set global variables for use in TAB completion */ + nmc_tab_completion.con_type = NM_SETTING_BOND_SETTING_NAME; + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) return FALSE; if (!master && ask) - master = master_ask = nmc_get_user_input (_("Bond master: ")); + master = master_ask = nmc_readline (PROMPT_BOND_MASTER); if (!master) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'master' is required.")); @@ -4499,11 +4489,14 @@ cleanup_team: {"config", TRUE, &config_c, FALSE}, {NULL} }; + /* Set global variables for use in TAB completion */ + nmc_tab_completion.con_type = NM_SETTING_TEAM_SETTING_NAME; + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) return FALSE; if (!master && ask) - master = master_ask = nmc_get_user_input (_("Team master: ")); + master = master_ask = nmc_readline (PROMPT_TEAM_MASTER); if (!master) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'master' is required.")); @@ -4703,11 +4696,14 @@ cleanup_bridge: {"hairpin", TRUE, &hairpin_c, FALSE}, {NULL} }; + /* Set global variables for use in TAB completion */ + nmc_tab_completion.con_type = NM_SETTING_BRIDGE_SETTING_NAME; + if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) return FALSE; if (!master && ask) - master = master_ask = nmc_get_user_input (_("Bridge master: ")); + master = master_ask = nmc_readline (PROMPT_BRIDGE_MASTER); if (!master) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'master' is required.")); @@ -4782,8 +4778,6 @@ cleanup_bridge_slave: } else if (!strcmp (con_type, NM_SETTING_VPN_SETTING_NAME)) { /* Build up the settings required for 'vpn' */ gboolean success = FALSE; - const char *known_vpns[] = { "openvpn", "vpnc", "pptp", "openconnect", "openswan", "libreswan", - "ssh", "l2tp", "iodine", NULL }; const char *vpn_type = NULL; char *vpn_type_ask = NULL; const char *user_c = NULL; @@ -4798,14 +4792,15 @@ cleanup_bridge_slave: return FALSE; if (!vpn_type && ask) - vpn_type = vpn_type_ask = nmc_get_user_input (_("VPN type: ")); + vpn_type = vpn_type_ask = nmc_readline (PROMPT_VPN_TYPE); if (!vpn_type) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'vpn-type' is required.")); goto cleanup_vpn; } + vpn_type = g_strstrip (vpn_type_ask); - if (!(st = nmc_string_is_valid (vpn_type, known_vpns, NULL))) { + if (!(st = nmc_string_is_valid (vpn_type, nmc_known_vpns, NULL))) { printf (_("Warning: 'vpn-type': %s not known.\n"), vpn_type); st = vpn_type; } @@ -4852,7 +4847,7 @@ cleanup_vpn: return FALSE; if (!ssid && ask) - ssid = ssid_ask = nmc_get_user_input (_("SSID: ")); + ssid = ssid_ask = nmc_readline (_("SSID: ")); if (!ssid) { g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT, _("Error: 'ssid' is required.")); @@ -5040,6 +5035,140 @@ update_connection (gboolean persistent, nm_remote_connection_commit_changes_unsaved (connection, callback, user_data); } +static char * +gen_func_vpn_types (char *text, int state) +{ + return nmc_rl_gen_func_basic (text, state, nmc_known_vpns); +} + +static char * +gen_func_bool_values_l10n (char *text, int state) +{ + const char *words[] = { WORD_LOC_YES, WORD_LOC_NO, NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_ib_type (char *text, int state) +{ + const char *words[] = { "datagram", "connected", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_bt_type (char *text, int state) +{ + const char *words[] = { "panu", "dun-gsm", "dun-cdma", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_bond_mode (char *text, int state) +{ + const char *words[] = { "balance-rr", "active-backup", "balance-xor", "broadcast", + "802.3ad", "balance-tlb", "balance-alb", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} +static char * +gen_func_bond_mon_mode (char *text, int state) +{ + const char *words[] = { "miimon", "arp", NULL }; + return nmc_rl_gen_func_basic (text, state, words); +} + +static char * +gen_func_master_ifnames (char *text, int state) +{ + GSList *iter; + GPtrArray *ifnames; + char *ret; + NMConnection *con; + NMSettingConnection *s_con; + const char *con_type, *ifname; + + if (!nm_cli.system_connections) + return NULL; + + /* Disable appending space after completion */ + rl_completion_append_character = '\0'; + + ifnames = g_ptr_array_sized_new (20); + for (iter = nm_cli.system_connections; iter; iter = g_slist_next (iter)) { + con = NM_CONNECTION (iter->data); + s_con = nm_connection_get_setting_connection (con); + g_assert (s_con); + con_type = nm_setting_connection_get_connection_type (s_con); + if (g_strcmp0 (con_type, nmc_tab_completion.con_type) != 0) + continue; + ifname = nm_connection_get_virtual_iface_name (con); + g_ptr_array_add (ifnames, (gpointer) ifname); + } + g_ptr_array_add (ifnames, (gpointer) NULL); + + ret = nmc_rl_gen_func_basic (text, state, (const char **) ifnames->pdata); + + g_ptr_array_free (ifnames, TRUE); + return ret; +} + +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; +} + +static char ** +nmcli_con_add_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; + + /* Restore standard append character to space */ + rl_completion_append_character = ' '; + + if (!is_single_word (rl_line_buffer)) + return NULL; + + if (g_strcmp0 (rl_prompt, PROMPT_CON_TYPE) == 0) + generator_func = gen_connection_types; + else if (g_strcmp0 (rl_prompt, PROMPT_VPN_TYPE) == 0) + generator_func = gen_func_vpn_types; + else if ( g_strcmp0 (rl_prompt, PROMPT_BOND_MASTER) == 0 + || g_strcmp0 (rl_prompt, PROMPT_TEAM_MASTER) == 0 + || g_strcmp0 (rl_prompt, PROMPT_BRIDGE_MASTER) == 0) + generator_func = gen_func_master_ifnames; + else if ( g_str_has_suffix (rl_prompt, prompt_yes_no (TRUE, NULL)) + || g_str_has_suffix (rl_prompt, prompt_yes_no (TRUE, ":")) + || g_str_has_suffix (rl_prompt, prompt_yes_no (FALSE, NULL)) + || g_str_has_suffix (rl_prompt, prompt_yes_no (FALSE, ":"))) + generator_func = gen_func_bool_values_l10n; + else if (g_str_has_suffix (rl_prompt, PROMPT_IB_MODE)) + generator_func = gen_func_ib_type; + else if (g_str_has_suffix (rl_prompt, PROMPT_BT_TYPE)) + generator_func = gen_func_bt_type; + else if (g_str_has_prefix (rl_prompt, PROMPT_BOND_MODE)) + generator_func = gen_func_bond_mode; + else if (g_str_has_suffix (rl_prompt, PROMPT_BOND_MON_MODE)) + generator_func = gen_func_bond_mon_mode; + + if (generator_func) + match_array = rl_completion_matches (text, generator_func); + + return match_array; +} + static NMCResultCode do_connection_add (NmCli *nmc, int argc, char **argv) { @@ -5067,6 +5196,8 @@ do_connection_add (NmCli *nmc, int argc, char **argv) {"save", TRUE, &save, FALSE}, {NULL} }; + rl_attempted_completion_function = (CPPFunction *) nmcli_con_add_tab_completion; + nmc->return_value = NMC_RESULT_SUCCESS; if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, &error)) { @@ -5079,7 +5210,7 @@ do_connection_add (NmCli *nmc, int argc, char **argv) if (!type && nmc->ask) { char *types_tmp = get_valid_options_string (nmc_valid_connection_types); printf ("Valid types: [%s]\n", types_tmp); - type = type_ask = nmc_get_user_input (_("Connection type: ")); + type = type_ask = nmc_readline (PROMPT_CON_TYPE); g_free (types_tmp); } if (!type) { @@ -5087,6 +5218,7 @@ do_connection_add (NmCli *nmc, int argc, char **argv) nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; goto error; } + type = g_strstrip (type_ask); if (!(setting_name = check_valid_name (type, nmc_valid_connection_types, &error))) { g_string_printf (nmc->return_text, _("Error: invalid connection type; %s."), @@ -5124,7 +5256,7 @@ do_connection_add (NmCli *nmc, int argc, char **argv) ifname_mandatory = FALSE; if (!ifname && ifname_mandatory && nmc->ask) { - ifname = ifname_ask = nmc_get_user_input (_("Interface name [*]: ")); + ifname = ifname_ask = nmc_readline (_("Interface name [*]: ")); if (!ifname) ifname = ifname_ask = g_strdup ("*"); } @@ -5215,46 +5347,7 @@ error: /*----------------------------------------------------------------------------*/ - -typedef char *CPFunction (); -typedef char **CPPFunction (); -/* History entry struct copied from libreadline's history.h */ -typedef struct _hist_entry { - char *line; - char *timestamp; - char *data; -} HIST_ENTRY; - -typedef char * (*ReadLineFunc) (const char *); -typedef void (*AddHistoryFunc) (const char *); -typedef HIST_ENTRY** (*HistoryListFunc) (void); -typedef int (*RlInsertTextFunc) (char *); -typedef char ** (*RlCompletionMatchesFunc) (char *, CPFunction *); - -typedef struct { - ReadLineFunc readline_func; - AddHistoryFunc add_history_func; - HistoryListFunc history_list_func; - RlInsertTextFunc rl_insert_text_func; - void **rl_startup_hook_x; - RlCompletionMatchesFunc completion_matches_func; - void **rl_attempted_completion_function_x; - void **rl_completion_entry_function_x; - char **rl_line_buffer_x; - char **rl_prompt_x; - int *rl_attempted_completion_over_x; - int *rl_complete_with_tilde_expansion_x; - int *rl_completion_append_character_x; - const char **rl_completer_word_break_characters_x; - void (*rl_free_line_state_func) (void); - void (*rl_cleanup_after_signal_func) (void); - void **rl_completion_display_matches_hook_x; - void (*rl_display_match_list_func) (char **, int, int); - void (*rl_forced_update_display_func) (void); -} EditLibSymbols; - -static EditLibSymbols edit_lib_symbols; -static char *pre_input_deftext; +/* Functions for readline TAB completion in editor */ static void uuid_display_hook (char **array, int len, int max_len) @@ -5275,52 +5368,30 @@ uuid_display_hook (char **array, int len, int max_len) max = strlen (id); } } - edit_lib_symbols.rl_display_match_list_func (array, len, max_len + max + 3); - edit_lib_symbols.rl_forced_update_display_func (); + rl_display_match_list (array, len, max_len + max + 3); + rl_forced_update_display (); } +static char *pre_input_deftext; static int set_deftext (void) { - if ( pre_input_deftext - && edit_lib_symbols.rl_insert_text_func - && edit_lib_symbols.rl_startup_hook_x) { - edit_lib_symbols.rl_insert_text_func (pre_input_deftext); + if (pre_input_deftext && rl_startup_hook) { + rl_insert_text (pre_input_deftext); g_free (pre_input_deftext); pre_input_deftext = NULL; - *edit_lib_symbols.rl_startup_hook_x = NULL; + rl_startup_hook = NULL; } return 0; } static char * -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; -} - -static char * gen_nmcli_cmds_menu (char *text, int state) { const char *words[] = { "goto", "set", "remove", "describe", "print", "verify", "save", "activate", "back", "help", "quit", "nmcli", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * @@ -5329,49 +5400,49 @@ gen_nmcli_cmds_submenu (char *text, int state) const char *words[] = { "set", "add", "change", "remove", "describe", "print", "back", "help", "quit", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * gen_cmd_nmcli (char *text, int state) { const char *words[] = { "status-line", "save-confirmation", "prompt-color", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * gen_cmd_nmcli_prompt_color (char *text, int state) { const char *words[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * gen_func_bool_values (char *text, int state) { const char *words[] = { "yes", "no", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * gen_cmd_verify0 (char *text, int state) { const char *words[] = { "all", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * gen_cmd_print2 (char *text, int state) { const char *words[] = { "setting", "connection", "all", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * gen_cmd_save (char *text, int state) { const char *words[] = { "persistent", "temporary", NULL }; - return gen_func_basic (text, state, words); + return nmc_rl_gen_func_basic (text, state, words); } static char * @@ -5433,7 +5504,7 @@ gen_property_names (char *text, int state) NMSetting *setting = NULL; char **valid_props = NULL; char *ret = NULL; - char *line = g_strdup (*edit_lib_symbols.rl_line_buffer_x); + const char *line = rl_line_buffer; const char *setting_name; char **strv = NULL; const NameItem *valid_settings_arr; @@ -5457,10 +5528,9 @@ gen_property_names (char *text, int state) if (setting) { valid_props = nmc_setting_get_valid_properties (setting); - ret = gen_func_basic (text, state, (const char **) valid_props); + ret = nmc_rl_gen_func_basic (text, state, (const char **) valid_props); } - g_free (line); g_strfreev (strv); g_strfreev (valid_props); if (setting) @@ -5494,7 +5564,7 @@ gen_compat_devices (char *text, int state) } compatible_devices[j] = NULL; - ret = gen_func_basic (text, state, compatible_devices); + ret = nmc_rl_gen_func_basic (text, state, compatible_devices); g_free (compatible_devices); return ret; @@ -5522,7 +5592,7 @@ gen_vpn_uuids (char *text, int state) } uuids[i] = NULL; - ret = gen_func_basic (text, state, uuids); + ret = nmc_rl_gen_func_basic (text, state, uuids); g_free (uuids); return ret; @@ -5707,6 +5777,9 @@ should_complete_vpn_uuids (const char *prompt, const char *line) return found; } +/* from readline */ +extern int rl_complete_with_tilde_expansion; + /* * Attempt to complete on the contents of TEXT. START and END show the * region of TEXT that contains the word to complete. We can use the @@ -5717,8 +5790,8 @@ static char ** nmcli_editor_tab_completion (char *text, int start, int end) { char **match_array = NULL; - const char *line = *edit_lib_symbols.rl_line_buffer_x; - const char *prompt = *edit_lib_symbols.rl_prompt_x; + const char *line = rl_line_buffer; + const char *prompt = rl_prompt; CPFunction *generator_func = NULL; gboolean copy_char; const char *p1; @@ -5728,16 +5801,16 @@ nmcli_editor_tab_completion (char *text, int start, int end) int num; /* Restore standard append character to space */ - *edit_lib_symbols.rl_completion_append_character_x = ' '; + rl_completion_append_character = ' '; /* Restore standard function for displaying matches */ - *edit_lib_symbols.rl_completion_display_matches_hook_x = NULL; + rl_completion_display_matches_hook = NULL; /* Disable default filename completion */ - *edit_lib_symbols.rl_attempted_completion_over_x = 1; + rl_attempted_completion_over = 1; /* Enable tilde expansion when filenames are completed */ - *edit_lib_symbols.rl_complete_with_tilde_expansion_x = 1; + rl_complete_with_tilde_expansion = 1; /* Filter out possible ANSI color escape sequences */ p1 = prompt; @@ -5764,6 +5837,9 @@ nmcli_editor_tab_completion (char *text, int start, int end) generator_func = gen_setting_names; else if (strcmp (prompt_tmp, EDITOR_PROMPT_PROPERTY) == 0) generator_func = gen_property_names; + else if ( g_str_has_suffix (rl_prompt, prompt_yes_no (TRUE, NULL)) + || g_str_has_suffix (rl_prompt, prompt_yes_no (FALSE, NULL))) + generator_func = gen_func_bool_values_l10n; else if (g_str_has_prefix (prompt_tmp, "nmcli")) { if (!strchr (prompt_tmp, '.')) { int level = g_str_has_prefix (prompt_tmp, "nmcli>") ? 0 : 1; @@ -5782,14 +5858,14 @@ nmcli_editor_tab_completion (char *text, int start, int end) if (num < 3) { if (level == 0 && (!dot || dot >= line + end)) { generator_func = gen_setting_names; - *edit_lib_symbols.rl_completion_append_character_x = '.'; + rl_completion_append_character = '.'; } else generator_func = gen_property_names; } else if (num >= 3) { if (num == 3 && should_complete_files (NULL, line)) - *edit_lib_symbols.rl_attempted_completion_over_x = 0; + rl_attempted_completion_over = 0; if (should_complete_vpn_uuids (NULL, line)) { - *edit_lib_symbols.rl_completion_display_matches_hook_x = uuid_display_hook; + rl_completion_display_matches_hook = uuid_display_hook; generator_func = gen_vpn_uuids; } } @@ -5798,7 +5874,7 @@ nmcli_editor_tab_completion (char *text, int start, int end) && num <= 2) { if (level == 0 && (!dot || dot >= line + end)) { generator_func = gen_setting_names; - *edit_lib_symbols.rl_completion_append_character_x = '.'; + rl_completion_append_character = '.'; } else generator_func = gen_property_names; } else if (should_complete_cmd (line, end, "nmcli", &num, &word)) { @@ -5825,9 +5901,9 @@ nmcli_editor_tab_completion (char *text, int start, int end) if ( should_complete_cmd (line, end, "add", &num, NULL) || should_complete_cmd (line, end, "set", &num, NULL)) { if (num <= 2 && should_complete_files (prompt_tmp, line)) - *edit_lib_symbols.rl_attempted_completion_over_x = 0; + rl_attempted_completion_over = 0; else if (should_complete_vpn_uuids (prompt_tmp, line)) { - *edit_lib_symbols.rl_completion_display_matches_hook_x = uuid_display_hook; + rl_completion_display_matches_hook = uuid_display_hook; generator_func = gen_vpn_uuids; } } @@ -5840,129 +5916,13 @@ nmcli_editor_tab_completion (char *text, int start, int end) } if (generator_func) - match_array = edit_lib_symbols.completion_matches_func (text, generator_func); + match_array = rl_completion_matches (text, generator_func); g_free (prompt_tmp); g_free (word); return match_array; } -static GModule * -load_cmd_line_edit_lib (void) -{ - GModule *module = NULL; - char *lib_path; - int i; - static const char * const edit_lib_table[] = { - "libreadline.so.6", /* GNU Readline library version 6 - latest */ - "libreadline.so.5", /* GNU Readline library version 5 - previous */ - "libedit.so.0", /* NetBSD Editline library port (http://www.thrysoee.dk/editline/) */ - }; - - /* Try to load a library for line editing */ - for (i = 0; i < G_N_ELEMENTS (edit_lib_table); i++) { - lib_path = g_module_build_path (NULL, edit_lib_table[i]); - module = g_module_open (lib_path, G_MODULE_BIND_LOCAL); - g_free (lib_path); - if (module) - break; - } - if (!module) - return NULL; - - if (!g_module_symbol (module, "readline", (gpointer) (&edit_lib_symbols.readline_func))) - goto error; - if (!g_module_symbol (module, "add_history", (gpointer) (&edit_lib_symbols.add_history_func))) - goto error; - if (!g_module_symbol (module, "history_list", (gpointer) (&edit_lib_symbols.history_list_func))) - goto error; - if (!g_module_symbol (module, "rl_insert_text", (gpointer) (&edit_lib_symbols.rl_insert_text_func))) - goto error; - if (!g_module_symbol (module, "rl_startup_hook", (gpointer) (&edit_lib_symbols.rl_startup_hook_x))) - goto error; - if (!g_module_symbol (module, "rl_attempted_completion_function", - (gpointer) (&edit_lib_symbols.rl_attempted_completion_function_x))) - goto error; - if (!g_module_symbol (module, "completion_matches", - (gpointer) (&edit_lib_symbols.completion_matches_func))) - goto error; - if (!g_module_symbol (module, "rl_line_buffer", - (gpointer) (&edit_lib_symbols.rl_line_buffer_x))) - goto error; - if (!g_module_symbol (module, "rl_prompt", - (gpointer) (&edit_lib_symbols.rl_prompt_x))) - goto error; - if (!g_module_symbol (module, "rl_attempted_completion_over", - (gpointer) (&edit_lib_symbols.rl_attempted_completion_over_x))) - goto error; - if (!g_module_symbol (module, "rl_complete_with_tilde_expansion", - (gpointer) (&edit_lib_symbols.rl_complete_with_tilde_expansion_x))) - goto error; - if (!g_module_symbol (module, "rl_completion_append_character", - (gpointer) (&edit_lib_symbols.rl_completion_append_character_x))) - goto error; - if (!g_module_symbol (module, "rl_completer_word_break_characters", - (gpointer) (&edit_lib_symbols.rl_completer_word_break_characters_x))) - goto error; - if (!g_module_symbol (module, "rl_free_line_state", - (gpointer) (&edit_lib_symbols.rl_free_line_state_func))) - goto error; - if (!g_module_symbol (module, "rl_cleanup_after_signal", - (gpointer) (&edit_lib_symbols.rl_cleanup_after_signal_func))) - goto error; - if (!g_module_symbol (module, "rl_completion_display_matches_hook", - (gpointer) (&edit_lib_symbols.rl_completion_display_matches_hook_x))) - goto error; - if (!g_module_symbol (module, "rl_display_match_list", - (gpointer) (&edit_lib_symbols.rl_display_match_list_func))) - goto error; - if (!g_module_symbol (module, "rl_forced_update_display", - (gpointer) (&edit_lib_symbols.rl_forced_update_display_func))) - goto error; - - /* Set a pointer to an alternative function to create matches */ - *edit_lib_symbols.rl_attempted_completion_function_x = (CPPFunction *) nmcli_editor_tab_completion; - - /* Use ' ' and '.' as word break characters */ - *edit_lib_symbols.rl_completer_word_break_characters_x = ". "; - - return module; -error: - g_module_close (module); - return NULL; -} - -void -nmc_cleanup_readline (void) -{ - if (edit_lib_symbols.rl_free_line_state_func) - edit_lib_symbols.rl_free_line_state_func (); - if (edit_lib_symbols.rl_cleanup_after_signal_func) - edit_lib_symbols.rl_cleanup_after_signal_func (); -} - -static char * -readline_x (const char *prompt) -{ - char *str; - - if (edit_lib_symbols.readline_func) { - str = edit_lib_symbols.readline_func (prompt); - /* Return NULL, not empty string */ - if (str && *str == '\0') { - g_free (str); - str = NULL; - } - } else - str = nmc_get_user_input (prompt); - - if (edit_lib_symbols.add_history_func && str && *str) - edit_lib_symbols.add_history_func (str); - - return str; -} - - #define NMCLI_EDITOR_HISTORY ".nmcli-history" static void @@ -5975,10 +5935,6 @@ load_history_cmds (const char *uuid) size_t i; GError *err = NULL; - /* Nothing to do if readline library is not used */ - if (!edit_lib_symbols.add_history_func) - return; - filename = g_build_filename (g_get_home_dir (), NMCLI_EDITOR_HISTORY, NULL); kf = g_key_file_new (); if (!g_key_file_load_from_file (kf, filename, G_KEY_FILE_KEEP_COMMENTS, &err)) { @@ -5992,7 +5948,7 @@ load_history_cmds (const char *uuid) for (i = 0; keys && keys[i]; i++) { line = g_key_file_get_string (kf, uuid, keys[i], NULL); if (line && *line) - edit_lib_symbols.add_history_func (line); + add_history (line); g_free (line); } g_strfreev (keys); @@ -6012,9 +5968,7 @@ save_history_cmds (const char *uuid) gsize len = 0; GError *err = NULL; - if (edit_lib_symbols.history_list_func) - hist = edit_lib_symbols.history_list_func(); - + hist = history_list (); if (hist) { filename = g_build_filename (g_get_home_dir (), NMCLI_EDITOR_HISTORY, NULL); kf = g_key_file_new (); @@ -6602,6 +6556,23 @@ is_connection_dirty (NMConnection *connection, NMRemoteConnection *remote) NM_SETTING_COMPARE_FLAG_EXACT); } +static gboolean +confirm_quit (void) +{ + char *answer; + gboolean want_quit = FALSE; + + answer = nmc_readline (_("The connection is not saved. " + "Do you really want to quit? %s"), + prompt_yes_no (FALSE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (answer && matches (answer, WORD_LOC_YES) == 0) + want_quit = TRUE; + + g_free (answer); + return want_quit; +} + /* * Submenu for detailed property editing * Return: TRUE - continue; FALSE - should quit @@ -6617,7 +6588,7 @@ property_edit_submenu (NmCli *nmc, NmcEditorSubCmd cmdsub; gboolean cmd_property_loop = TRUE; gboolean should_quit = FALSE; - char *prop_val_user, *tmp_prompt; + char *prop_val_user; gboolean set_result; GError *tmp_err = NULL; char *prompt; @@ -6645,7 +6616,7 @@ property_edit_submenu (NmCli *nmc, if (nmc->editor_status_line) editor_show_status_line (connection, dirty, temp_changes); - cmd_property_user = readline_x (prompt); + cmd_property_user = nmc_readline ("%s", prompt); if (!cmd_property_user || *cmd_property_user == '\0') continue; cmdsub = parse_editor_sub_cmd (g_strstrip (cmd_property_user), &cmd_property_arg); @@ -6657,11 +6628,9 @@ property_edit_submenu (NmCli *nmc, * ADD adds the new value(s) * single values: : both SET and ADD sets the new value */ - if (!cmd_property_arg) { - tmp_prompt = g_strdup_printf (_("Enter '%s' value: "), prop_name); - prop_val_user = readline_x (tmp_prompt); - g_free (tmp_prompt); - } else + if (!cmd_property_arg) + prop_val_user = nmc_readline (_("Enter '%s' value: "), prop_name); + else prop_val_user = g_strdup (cmd_property_arg); /* nmc_setting_set_property() only adds new value, thus we have to @@ -6685,10 +6654,9 @@ property_edit_submenu (NmCli *nmc, break; case NMC_EDITOR_SUB_CMD_CHANGE: - *edit_lib_symbols.rl_startup_hook_x = set_deftext; + rl_startup_hook = set_deftext; pre_input_deftext = nmc_setting_get_property_out2in (curr_setting, prop_name, NULL); - tmp_prompt = g_strdup_printf (_("Edit '%s' value: "), prop_name); - prop_val_user = readline_x (tmp_prompt); + prop_val_user = nmc_readline (_("Edit '%s' value: "), prop_name); nmc_property_get_gvalue (curr_setting, prop_name, &prop_g_value); nmc_property_set_default_value (curr_setting, prop_name); @@ -6699,7 +6667,6 @@ property_edit_submenu (NmCli *nmc, nmc_property_set_gvalue (curr_setting, prop_name, &prop_g_value); } g_free (prop_val_user); - g_free (tmp_prompt); if (G_IS_VALUE (&prop_g_value)) g_value_unset (&prop_g_value); break; @@ -6761,16 +6728,10 @@ property_edit_submenu (NmCli *nmc, case NMC_EDITOR_SUB_CMD_QUIT: if (is_connection_dirty (connection, *rem_con)) { - char *tmp_str; - do { - tmp_str = nmc_get_user_input (_("The connection is not saved. " - "Do you really want to quit? [y/n]\n")); - } while (!tmp_str); - if (matches (tmp_str, "yes") == 0) { + if (confirm_quit ()) { cmd_property_loop = FALSE; should_quit = TRUE; /* we will quit nmcli */ } - g_free (tmp_str); } else { cmd_property_loop = FALSE; should_quit = TRUE; /* we will quit nmcli */ @@ -6876,7 +6837,7 @@ ask_check_setting (const char *arg, if (!arg) { printf (_("Available settings: %s\n"), valid_settings_str); - setting_name_user = nmc_get_user_input (EDITOR_PROMPT_SETTING); + setting_name_user = nmc_readline (EDITOR_PROMPT_SETTING); } else setting_name_user = g_strdup (arg); @@ -6902,7 +6863,7 @@ ask_check_property (const char *arg, if (!arg) { printf (_("Available properties: %s\n"), valid_props_str); - prop_name_user = readline_x (EDITOR_PROMPT_PROPERTY); + prop_name_user = nmc_readline (EDITOR_PROMPT_PROPERTY); if (prop_name_user) g_strstrip (prop_name_user); } else @@ -6950,10 +6911,11 @@ confirm_connection_saving (NMConnection *local, NMConnection *remote) if (ac_local && !ac_remote) { char *answer; - answer = nmc_get_user_input (_("Saving the connection with 'autoconnect=yes'. " - "That might result in an immediate activation of the connection.\n" - "Do you still want to save? [yes] ")); - if (!answer || matches (answer, "yes") == 0) + answer = nmc_readline (_("Saving the connection with 'autoconnect=yes'. " + "That might result in an immediate activation of the connection.\n" + "Do you still want to save? %s"), prompt_yes_no (TRUE, NULL)); + answer = answer ? g_strstrip (answer) : NULL; + if (!answer || matches (answer, WORD_LOC_YES) == 0) confirmed = TRUE; else confirmed = FALSE; @@ -7046,7 +7008,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t editor_show_status_line (connection, dirty, temp_changes); /* Read user input */ - cmd_user = readline_x (menu_ctx.main_prompt); + cmd_user = nmc_readline ("%s", menu_ctx.main_prompt); /* Get the remote connection again, it may have disapeared */ removed = refresh_remote_connection (&weak, &rem_con); @@ -7069,7 +7031,6 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t if (menu_ctx.level == 1) { const char *prop_name; char *prop_val_user = NULL; - char *tmp_prompt; const char *avals; GError *tmp_err = NULL; @@ -7083,9 +7044,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t if (avals) printf (_("Allowed values for '%s' property: %s\n"), prop_name, avals); - tmp_prompt = g_strdup_printf (_("Enter '%s' value: "), prop_name); - prop_val_user = readline_x (tmp_prompt); - g_free (tmp_prompt); + prop_val_user = nmc_readline (_("Enter '%s' value: "), prop_name); /* Set property value */ if (!nmc_setting_set_property (menu_ctx.curr_setting, prop_name, prop_val_user, &tmp_err)) { @@ -7100,7 +7059,6 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t NMSetting *ss = NULL; gboolean created_ss = FALSE; char *prop_name; - char *tmp_prompt; GError *tmp_err = NULL; if (cmd_arg_s) { @@ -7141,9 +7099,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t if (avals) printf (_("Allowed values for '%s' property: %s\n"), prop_name, avals); - tmp_prompt = g_strdup_printf (_("Enter '%s' value: "), prop_name); - cmd_arg_v = readline_x (tmp_prompt); - g_free (tmp_prompt); + cmd_arg_v = nmc_readline (_("Enter '%s' value: "), prop_name); } /* Set property value */ @@ -7631,14 +7587,8 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t case NMC_EDITOR_MAIN_CMD_QUIT: if (is_connection_dirty (connection, rem_con)) { - char *tmp_str; - do { - tmp_str = nmc_get_user_input (_("The connection is not saved. " - "Do you really want to quit? [y/n]\n")); - } while (!tmp_str); - if (matches (tmp_str, "yes") == 0) + if (confirm_quit ()) cmd_loop = FALSE; /* quit command loop */ - g_free (tmp_str); } else cmd_loop = FALSE; /* quit command loop */ break; @@ -7833,7 +7783,6 @@ do_connection_edit (NmCli *nmc, int argc, char **argv) char *tmp_str; GError *error = NULL; GError *err1 = NULL; - GModule *edit_lib_module = NULL; nmc_arg_t exp_args[] = { {"type", TRUE, &type, FALSE}, {"con-name", TRUE, &con_name, FALSE}, {"id", TRUE, &con_id, FALSE}, @@ -7854,19 +7803,11 @@ do_connection_edit (NmCli *nmc, int argc, char **argv) } } - /* Load line editing library */ - if (!(edit_lib_module = load_cmd_line_edit_lib ())) { - printf (_(">>> Command-line editing is not available. " - "Consider installing a line editing library to enable the feature. <<<\n" - "Supported libraries are:\n" - " - GNU Readline (libreadline) http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html\n" - " - NetBSD Editline (libedit) http://www.thrysoee.dk/editline/\n")); - edit_lib_symbols.readline_func = NULL; - edit_lib_symbols.add_history_func = NULL; - edit_lib_symbols.history_list_func = NULL; - edit_lib_symbols.rl_insert_text_func = NULL; - edit_lib_symbols.rl_startup_hook_x = NULL; - } + /* Setup some readline completion stuff */ + /* Set a pointer to an alternative function to create matches */ + rl_attempted_completion_function = (CPPFunction *) nmcli_editor_tab_completion; + /* Use ' ' and '.' as word break characters */ + rl_completer_word_break_characters = ". "; if (!con) { if (con_id && !con_uuid && !con_path) { @@ -7931,7 +7872,7 @@ do_connection_edit (NmCli *nmc, int argc, char **argv) printf (_("Error: invalid connection type; %s\n"), err1->message); g_clear_error (&err1); - type_ask = readline_x (EDITOR_PROMPT_CON_TYPE); + type_ask = nmc_readline (EDITOR_PROMPT_CON_TYPE); type = type_ask = type_ask ? g_strstrip (type_ask) : NULL; connection_type = check_valid_name (type_ask, nmc_valid_connection_types, &err1); g_free (type_ask); @@ -7984,9 +7925,6 @@ do_connection_edit (NmCli *nmc, int argc, char **argv) /* Run menu loop */ editor_menu_main (nmc, connection, connection_type); - if (edit_lib_module) - g_module_close (edit_lib_module); - if (connection) g_object_unref (connection); g_free (nmc_tab_completion.con_type); @@ -8268,7 +8206,7 @@ do_connection_delete (NmCli *nmc, int argc, char **argv) if (argc == 0) { if (nmc->ask) { - line = nmc_get_user_input (_("Connection (name, UUID, or path): ")); + line = nmc_readline (PROMPT_CONNECTION); nmc_string_to_arg_array (line, "", &arg_arr, &arg_num); arg_ptr = arg_arr; } @@ -8444,11 +8382,62 @@ connection_editor_thread_func (gpointer data) return NULL; } +static char * +gen_func_connection_names (char *text, int state) +{ + int i = 0; + GSList *iter; + const char **connections; + char *ret; + + if (!nm_cli.system_connections) + return NULL; + + connections = g_new (const char *, g_slist_length (nm_cli.system_connections) + 1); + for (iter = nm_cli.system_connections; iter; iter = g_slist_next (iter)) { + NMConnection *con = NM_CONNECTION (iter->data); + const char *id = nm_connection_get_id (con); + connections[i++] = id; + } + connections[i] = NULL; + + ret = nmc_rl_gen_func_basic (text, state, connections); + + g_free (connections); + return ret; +} + +static char ** +nmcli_con_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_CONNECTION) == 0) + generator_func = gen_func_connection_names; + + if (generator_func) + match_array = rl_completion_matches (text, generator_func); + + return match_array; +} + static NMCResultCode parse_cmd (NmCli *nmc, int argc, char **argv) { GError *error = NULL; + rl_attempted_completion_function = (CPPFunction *) nmcli_con_tab_completion; + if (argc == 0) { if (!nmc_terse_option_check (nmc->print_output, nmc->required_fields, &error)) goto opt_error; diff --git a/cli/src/connections.h b/cli/src/connections.h index 4ccd8c81cc..c736859799 100644 --- a/cli/src/connections.h +++ b/cli/src/connections.h @@ -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 */ diff --git a/cli/src/devices.c b/cli/src/devices.c index a789b0b954..f362449ee4 100644 --- a/cli/src/devices.c +++ b/cli/src/devices.c @@ -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; diff --git a/cli/src/nmcli.c b/cli/src/nmcli.c index 1e447485ba..66288c20ac 100644 --- a/cli/src/nmcli.c +++ b/cli/src/nmcli.c @@ -37,6 +37,7 @@ #include "nmcli.h" #include "utils.h" +#include "common.h" #include "connections.h" #include "devices.h" #include "network-manager.h" diff --git a/configure.ac b/configure.ac index cfccfc2e00..066c9d54f8 100644 --- a/configure.ac +++ b/configure.ac @@ -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]), |