* [PATCH v3 1/6] shared/shell: Add initial implementation
@ 2017-11-16 10:59 Luiz Augusto von Dentz
2017-11-16 10:59 ` [PATCH v3 2/6] client: Make use of bt_shell Luiz Augusto von Dentz
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Luiz Augusto von Dentz @ 2017-11-16 10:59 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This add initial bt_shell helper which can be used to create shell-like
command line tools.
---
v3: Add submenu changes
Makefile.tools | 3 +-
src/shared/shell.c | 570 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/shared/shell.h | 67 +++++++
3 files changed, 639 insertions(+), 1 deletion(-)
create mode 100644 src/shared/shell.c
create mode 100644 src/shared/shell.h
diff --git a/Makefile.tools b/Makefile.tools
index 561302fa1..dc2902cb7 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -8,7 +8,8 @@ client_bluetoothctl_SOURCES = client/main.c \
client/advertising.h \
client/advertising.c \
client/gatt.h client/gatt.c \
- monitor/uuid.h monitor/uuid.c
+ monitor/uuid.h monitor/uuid.c \
+ src/shared/shell.h src/shared/shell.c
client_bluetoothctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \
@GLIB_LIBS@ @DBUS_LIBS@ -lreadline
endif
diff --git a/src/shared/shell.c b/src/shared/shell.c
new file mode 100644
index 000000000..7db629bf1
--- /dev/null
+++ b/src/shared/shell.c
@@ -0,0 +1,570 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2017 Intel Corporation. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <sys/signalfd.h>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <glib.h>
+
+#include "src/shared/io.h"
+#include "src/shared/util.h"
+#include "src/shared/queue.h"
+#include "src/shared/shell.h"
+
+#define CMD_LENGTH 48
+#define print_text(color, fmt, args...) \
+ printf(color fmt COLOR_OFF "\n", ## args)
+#define print_menu(cmd, args, desc) \
+ printf(COLOR_HIGHLIGHT "%s %-*s " COLOR_OFF "%s\n", \
+ cmd, (int)(CMD_LENGTH - strlen(cmd)), args, desc)
+
+static GMainLoop *main_loop;
+static gboolean option_version = FALSE;
+
+static struct {
+ struct io *input;
+
+ bool saved_prompt;
+ bt_shell_prompt_input_func saved_func;
+ void *saved_user_data;
+
+ const struct bt_shell_menu_entry *menu;
+ /* TODO: Add submenus support */
+} data;
+
+static void shell_print_menu(void);
+
+static void cmd_version(const char *arg)
+{
+ bt_shell_printf("Version %s\n", VERSION);
+}
+
+static void cmd_quit(const char *arg)
+{
+ g_main_loop_quit(main_loop);
+}
+
+static void cmd_help(const char *arg)
+{
+ shell_print_menu();
+}
+
+static const struct bt_shell_menu_entry default_menu[] = {
+ { "version", NULL, cmd_version, "Display version" },
+ { "quit", NULL, cmd_quit, "Quit program" },
+ { "exit", NULL, cmd_quit, "Quit program" },
+ { "help", NULL, cmd_help,
+ "Display help about this program" },
+ { }
+};
+
+static void shell_print_menu(void)
+{
+ const struct bt_shell_menu_entry *entry;
+
+ if (!data.menu)
+ return;
+
+ print_text(COLOR_HIGHLIGHT, "Available commands:");
+ print_text(COLOR_HIGHLIGHT, "-------------------");
+ for (entry = data.menu; entry->cmd; entry++) {
+ print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
+ }
+
+ for (entry = default_menu; entry->cmd; entry++) {
+ print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : "");
+ }
+}
+
+static void shell_exec(const char *cmd, const char *arg)
+{
+ const struct bt_shell_menu_entry *entry;
+
+ if (!data.menu || !cmd)
+ return;
+
+ for (entry = data.menu; entry->cmd; entry++) {
+ if (strcmp(cmd, entry->cmd))
+ continue;
+
+ if (entry->func) {
+ entry->func(arg);
+ return;
+ }
+ }
+
+ for (entry = default_menu; entry->cmd; entry++) {
+ if (strcmp(cmd, entry->cmd))
+ continue;
+
+ if (entry->func) {
+ entry->func(arg);
+ return;
+ }
+ }
+
+ print_text(COLOR_HIGHLIGHT, "Invalid command");
+}
+
+void bt_shell_printf(const char *fmt, ...)
+{
+ va_list args;
+ bool save_input;
+ char *saved_line;
+ int saved_point;
+
+ save_input = !RL_ISSTATE(RL_STATE_DONE);
+
+ if (save_input) {
+ saved_point = rl_point;
+ saved_line = rl_copy_text(0, rl_end);
+ if (!data.saved_prompt) {
+ rl_save_prompt();
+ rl_replace_line("", 0);
+ rl_redisplay();
+ }
+ }
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+
+ if (save_input) {
+ if (!data.saved_prompt)
+ rl_restore_prompt();
+ rl_replace_line(saved_line, 0);
+ rl_point = saved_point;
+ rl_forced_update_display();
+ free(saved_line);
+ }
+}
+
+void bt_shell_hexdump(const unsigned char *buf, size_t len)
+{
+ static const char hexdigits[] = "0123456789abcdef";
+ char str[68];
+ size_t i;
+
+ if (!len)
+ return;
+
+ str[0] = ' ';
+
+ for (i = 0; i < len; i++) {
+ str[((i % 16) * 3) + 1] = ' ';
+ str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4];
+ str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf];
+ str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.';
+
+ if ((i + 1) % 16 == 0) {
+ str[49] = ' ';
+ str[50] = ' ';
+ str[67] = '\0';
+ bt_shell_printf("%s\n", str);
+ str[0] = ' ';
+ }
+ }
+
+ if (i % 16 > 0) {
+ size_t j;
+ for (j = (i % 16); j < 16; j++) {
+ str[(j * 3) + 1] = ' ';
+ str[(j * 3) + 2] = ' ';
+ str[(j * 3) + 3] = ' ';
+ str[j + 51] = ' ';
+ }
+ str[49] = ' ';
+ str[50] = ' ';
+ str[67] = '\0';
+ bt_shell_printf("%s\n", str);
+ }
+}
+
+void bt_shell_prompt_input(const char *label, const char *msg,
+ bt_shell_prompt_input_func func, void *user_data)
+{
+ /* Normal use should not prompt for user input to the value a second
+ * time before it releases the prompt, but we take a safe action. */
+ if (data.saved_prompt)
+ return;
+
+ rl_save_prompt();
+ rl_message(COLOR_RED "[%s]" COLOR_OFF " %s ", label, msg);
+
+ data.saved_prompt = true;
+ data.saved_func = func;
+ data.saved_user_data = user_data;
+}
+
+int bt_shell_release_prompt(const char *input)
+{
+ bt_shell_prompt_input_func func;
+ void *user_data;
+
+ if (!data.saved_prompt)
+ return -1;
+
+ data.saved_prompt = false;
+
+ rl_restore_prompt();
+
+ func = data.saved_func;
+ user_data = data.saved_user_data;
+
+ data.saved_func = NULL;
+ data.saved_user_data = NULL;
+
+ func(input, user_data);
+
+ return 0;
+}
+
+static void rl_handler(char *input)
+{
+ char *cmd, *arg;
+
+ if (!input) {
+ rl_insert_text("quit");
+ rl_redisplay();
+ rl_crlf();
+ g_main_loop_quit(main_loop);
+ return;
+ }
+
+ if (!strlen(input))
+ goto done;
+
+ if (!bt_shell_release_prompt(input))
+ goto done;
+
+ if (history_search(input, -1))
+ add_history(input);
+
+ cmd = strtok_r(input, " ", &arg);
+ if (!cmd)
+ goto done;
+
+ if (arg) {
+ int len = strlen(arg);
+ if (len > 0 && arg[len - 1] == ' ')
+ arg[len - 1] = '\0';
+ }
+
+ shell_exec(cmd, arg);
+done:
+ free(input);
+}
+
+static char *cmd_generator(const char *text, int state)
+{
+ static const struct bt_shell_menu_entry *entry;
+ static int index, len;
+ const char *cmd;
+
+ if (!state) {
+ entry = default_menu;
+ index = 0;
+ len = strlen(text);
+ }
+
+ while ((cmd = entry[index].cmd)) {
+ index++;
+
+ if (!strncmp(cmd, text, len))
+ return strdup(cmd);
+ }
+
+ if (state)
+ return NULL;
+
+ entry = data.menu;
+ index = 0;
+
+ return cmd_generator(text, 1);
+}
+
+static char **menu_completion(const struct bt_shell_menu_entry *entry,
+ const char *text, char *input_cmd)
+{
+ char **matches = NULL;
+
+ for (entry = data.menu; entry->cmd; entry++) {
+ if (strcmp(entry->cmd, input_cmd))
+ continue;
+
+ if (!entry->gen)
+ continue;
+
+ rl_completion_display_matches_hook = entry->disp;
+ matches = rl_completion_matches(text, entry->gen);
+ break;
+ }
+
+ return matches;
+}
+
+static char **shell_completion(const char *text, int start, int end)
+{
+ char **matches = NULL;
+
+ if (!data.menu)
+ return NULL;
+
+ if (start > 0) {
+ char *input_cmd;
+
+ input_cmd = strndup(rl_line_buffer, start - 1);
+ matches = menu_completion(default_menu, text, input_cmd);
+ if (!matches)
+ matches = menu_completion(data.menu, text,
+ input_cmd);
+
+ free(input_cmd);
+ } else {
+ rl_completion_display_matches_hook = NULL;
+ matches = rl_completion_matches(text, cmd_generator);
+ }
+
+ if (!matches)
+ rl_attempted_completion_over = 1;
+
+ return matches;
+}
+
+static bool io_hup(struct io *io, void *user_data)
+{
+ g_main_loop_quit(main_loop);
+
+ return false;
+}
+
+static bool signal_read(struct io *io, void *user_data)
+{
+ static bool terminated = false;
+ struct signalfd_siginfo si;
+ ssize_t result;
+ int fd;
+
+ fd = io_get_fd(io);
+
+ result = read(fd, &si, sizeof(si));
+ if (result != sizeof(si))
+ return false;
+
+ switch (si.ssi_signo) {
+ case SIGINT:
+ if (data.input) {
+ rl_replace_line("", 0);
+ rl_crlf();
+ rl_on_new_line();
+ rl_redisplay();
+ break;
+ }
+
+ /*
+ * If input was not yet setup up that means signal was received
+ * while daemon was not yet running. Since user is not able
+ * to terminate client by CTRL-D or typing exit treat this as
+ * exit and fall through.
+ */
+
+ /* fall through */
+ case SIGTERM:
+ if (!terminated) {
+ rl_replace_line("", 0);
+ rl_crlf();
+ g_main_loop_quit(main_loop);
+ }
+
+ terminated = true;
+ break;
+ }
+
+ return false;
+}
+
+static struct io *setup_signalfd(void)
+{
+ struct io *io;
+ sigset_t mask;
+ int fd;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+
+ if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
+ perror("Failed to set signal mask");
+ return 0;
+ }
+
+ fd = signalfd(-1, &mask, 0);
+ if (fd < 0) {
+ perror("Failed to create signal descriptor");
+ return 0;
+ }
+
+ io = io_new(fd);
+
+ io_set_close_on_destroy(io, true);
+ io_set_read_handler(io, signal_read, NULL, NULL);
+ io_set_disconnect_handler(io, io_hup, NULL, NULL);
+
+ return io;
+}
+
+static GOptionEntry options[] = {
+ { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version,
+ "Show version information and exit" },
+ { NULL },
+};
+
+static void rl_init(void)
+{
+ setlinebuf(stdout);
+ rl_attempted_completion_function = shell_completion;
+
+ rl_erase_empty_line = 1;
+ rl_callback_handler_install(NULL, rl_handler);
+}
+
+void bt_shell_init(int *argc, char ***argv)
+{
+ GOptionContext *context;
+ GError *error = NULL;
+
+ context = g_option_context_new(NULL);
+ g_option_context_add_main_entries(context, options, NULL);
+
+ if (g_option_context_parse(context, argc, argv, &error) == FALSE) {
+ if (error != NULL) {
+ g_printerr("%s\n", error->message);
+ g_error_free(error);
+ } else
+ g_printerr("An unknown error occurred\n");
+ exit(1);
+ }
+
+ g_option_context_free(context);
+
+ if (option_version == TRUE) {
+ g_print("%s\n", VERSION);
+ exit(EXIT_SUCCESS);
+ }
+
+ main_loop = g_main_loop_new(NULL, FALSE);
+
+ rl_init();
+}
+
+static void rl_cleanup(void)
+{
+ rl_message("");
+ rl_callback_handler_remove();
+}
+
+void bt_shell_run(void)
+{
+ struct io *signal;
+
+ signal = setup_signalfd();
+
+ g_main_loop_run(main_loop);
+
+ bt_shell_release_prompt("");
+ bt_shell_detach();
+
+ io_destroy(signal);
+
+ g_main_loop_unref(main_loop);
+ main_loop = NULL;
+
+ rl_cleanup();
+}
+
+bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu)
+{
+ if (data.menu || !menu)
+ return false;
+
+ data.menu = menu;
+
+ return true;
+}
+
+void bt_shell_set_prompt(const char *string)
+{
+ if (!main_loop)
+ return;
+
+ rl_set_prompt(string);
+ printf("\r");
+ rl_on_new_line();
+ rl_redisplay();
+}
+
+static bool input_read(struct io *io, void *user_data)
+{
+ rl_callback_read_char();
+ return true;
+}
+
+bool bt_shell_attach(int fd)
+{
+ struct io *io;
+
+ /* TODO: Allow more than one input? */
+ if (data.input)
+ return false;
+
+ io = io_new(fd);
+
+ io_set_read_handler(io, input_read, NULL, NULL);
+ io_set_disconnect_handler(io, io_hup, NULL, NULL);
+
+ data.input = io;
+
+ return true;
+}
+
+bool bt_shell_detach(void)
+{
+ if (!data.input)
+ return false;
+
+ io_destroy(data.input);
+ data.input = NULL;
+
+ return true;
+}
diff --git a/src/shared/shell.h b/src/shared/shell.h
new file mode 100644
index 000000000..843335784
--- /dev/null
+++ b/src/shared/shell.h
@@ -0,0 +1,67 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2017 Intel Corporation. All rights reserved.
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define COLOR_OFF "\x1B[0m"
+#define COLOR_RED "\x1B[0;91m"
+#define COLOR_GREEN "\x1B[0;92m"
+#define COLOR_YELLOW "\x1B[0;93m"
+#define COLOR_BLUE "\x1B[0;94m"
+#define COLOR_BOLDGRAY "\x1B[1;30m"
+#define COLOR_BOLDWHITE "\x1B[1;37m"
+#define COLOR_HIGHLIGHT "\x1B[1;39m"
+
+typedef void (*bt_shell_menu_cb_t)(const char *arg);
+typedef char * (*bt_shell_menu_gen_t)(const char *text, int state);
+typedef void (*bt_shell_menu_disp_t) (char **matches, int num_matches,
+ int max_length);
+typedef void (*bt_shell_prompt_input_func) (const char *input, void *user_data);
+
+struct bt_shell_menu_entry {
+ const char *cmd;
+ const char *arg;
+ bt_shell_menu_cb_t func;
+ const char *desc;
+ bt_shell_menu_gen_t gen;
+ bt_shell_menu_disp_t disp;
+};
+
+void bt_shell_init(int *argc, char ***argv);
+
+void bt_shell_run(void);
+
+bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu);
+
+void bt_shell_set_prompt(const char *string);
+
+void bt_shell_printf(const char *fmt,
+ ...) __attribute__((format(printf, 1, 2)));
+void bt_shell_hexdump(const unsigned char *buf, size_t len);
+
+void bt_shell_prompt_input(const char *label, const char *msg,
+ bt_shell_prompt_input_func func, void *user_data);
+int bt_shell_release_prompt(const char *input);
+
+bool bt_shell_attach(int fd);
+bool bt_shell_detach(void);
+
+void bt_shell_cleanup(void);
--
2.13.6
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v3 2/6] client: Make use of bt_shell 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz @ 2017-11-16 10:59 ` Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 3/6] shared/shell: Add submenu support Luiz Augusto von Dentz ` (4 subsequent siblings) 5 siblings, 0 replies; 7+ messages in thread From: Luiz Augusto von Dentz @ 2017-11-16 10:59 UTC (permalink / raw) To: linux-bluetooth From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> Use bt_shell instead of readline directly. --- client/advertising.c | 42 +++---- client/agent.c | 72 +++++------ client/gatt.c | 149 ++++++++++++----------- client/main.c | 333 ++------------------------------------------------- 4 files changed, 146 insertions(+), 450 deletions(-) diff --git a/client/advertising.c b/client/advertising.c index 56093f387..f51f713b5 100644 --- a/client/advertising.c +++ b/client/advertising.c @@ -29,11 +29,11 @@ #include <stdlib.h> #include <stdint.h> #include <stdbool.h> -#include <readline/readline.h> +#include <string.h> #include <wordexp.h> #include "gdbus/gdbus.h" -#include "display.h" +#include "src/shared/shell.h" #include "advertising.h" #define AD_PATH "/org/bluez/advertising" @@ -82,7 +82,7 @@ static void ad_release(DBusConnection *conn) static DBusMessage *release_advertising(DBusConnection *conn, DBusMessage *msg, void *user_data) { - rl_printf("Advertising released\n"); + bt_shell_printf("Advertising released\n"); ad_release(conn); @@ -117,14 +117,14 @@ static void register_reply(DBusMessage *message, void *user_data) if (dbus_set_error_from_message(&error, message) == FALSE) { ad.registered = true; - rl_printf("Advertising object registered\n"); + bt_shell_printf("Advertising object registered\n"); } else { - rl_printf("Failed to register advertisement: %s\n", error.name); + bt_shell_printf("Failed to register advertisement: %s\n", error.name); dbus_error_free(&error); if (g_dbus_unregister_interface(conn, AD_PATH, AD_IFACE) == FALSE) - rl_printf("Failed to unregister advertising object\n"); + bt_shell_printf("Failed to unregister advertising object\n"); } } @@ -368,7 +368,7 @@ static const GDBusPropertyTable ad_props[] = { void ad_register(DBusConnection *conn, GDBusProxy *manager, const char *type) { if (ad.registered) { - rl_printf("Advertisement is already registered\n"); + bt_shell_printf("Advertisement is already registered\n"); return; } @@ -377,14 +377,14 @@ void ad_register(DBusConnection *conn, GDBusProxy *manager, const char *type) if (g_dbus_register_interface(conn, AD_PATH, AD_IFACE, ad_methods, NULL, ad_props, NULL, NULL) == FALSE) { - rl_printf("Failed to register advertising object\n"); + bt_shell_printf("Failed to register advertising object\n"); return; } if (g_dbus_proxy_method_call(manager, "RegisterAdvertisement", register_setup, register_reply, conn, NULL) == FALSE) { - rl_printf("Failed to register advertising\n"); + bt_shell_printf("Failed to register advertising\n"); return; } } @@ -405,12 +405,12 @@ static void unregister_reply(DBusMessage *message, void *user_data) if (dbus_set_error_from_message(&error, message) == FALSE) { ad.registered = false; - rl_printf("Advertising object unregistered\n"); + bt_shell_printf("Advertising object unregistered\n"); if (g_dbus_unregister_interface(conn, AD_PATH, AD_IFACE) == FALSE) - rl_printf("Failed to unregister advertising object\n"); + bt_shell_printf("Failed to unregister advertising object\n"); } else { - rl_printf("Failed to unregister advertisement: %s\n", + bt_shell_printf("Failed to unregister advertisement: %s\n", error.name); dbus_error_free(&error); } @@ -430,7 +430,7 @@ void ad_unregister(DBusConnection *conn, GDBusProxy *manager) if (g_dbus_proxy_method_call(manager, "UnregisterAdvertisement", unregister_setup, unregister_reply, conn, NULL) == FALSE) { - rl_printf("Failed to unregister advertisement method\n"); + bt_shell_printf("Failed to unregister advertisement method\n"); return; } } @@ -446,7 +446,7 @@ void ad_advertise_uuids(DBusConnection *conn, const char *arg) ad.uuids = g_strsplit(arg, " ", -1); if (!ad.uuids) { - rl_printf("Failed to parse input\n"); + bt_shell_printf("Failed to parse input\n"); return; } @@ -468,7 +468,7 @@ void ad_advertise_service(DBusConnection *conn, const char *arg) struct ad_data *data; if (wordexp(arg, &w, WRDE_NOCMD)) { - rl_printf("Invalid argument\n"); + bt_shell_printf("Invalid argument\n"); return; } @@ -485,14 +485,14 @@ void ad_advertise_service(DBusConnection *conn, const char *arg) char *endptr = NULL; if (i >= G_N_ELEMENTS(data->data)) { - rl_printf("Too much data\n"); + bt_shell_printf("Too much data\n"); ad_clear_service(); goto done; } val = strtol(w.we_wordv[i], &endptr, 0); if (!endptr || *endptr != '\0' || val > UINT8_MAX) { - rl_printf("Invalid value at index %d\n", i); + bt_shell_printf("Invalid value at index %d\n", i); ad_clear_service(); goto done; } @@ -521,7 +521,7 @@ void ad_advertise_manufacturer(DBusConnection *conn, const char *arg) struct ad_data *data; if (wordexp(arg, &w, WRDE_NOCMD)) { - rl_printf("Invalid argument\n"); + bt_shell_printf("Invalid argument\n"); return; } @@ -532,7 +532,7 @@ void ad_advertise_manufacturer(DBusConnection *conn, const char *arg) val = strtol(w.we_wordv[0], &endptr, 0); if (!endptr || *endptr != '\0' || val > UINT16_MAX) { - rl_printf("Invalid manufacture id\n"); + bt_shell_printf("Invalid manufacture id\n"); goto done; } @@ -541,14 +541,14 @@ void ad_advertise_manufacturer(DBusConnection *conn, const char *arg) for (i = 1; i < w.we_wordc; i++) { if (i >= G_N_ELEMENTS(data->data)) { - rl_printf("Too much data\n"); + bt_shell_printf("Too much data\n"); ad_clear_manufacturer(); goto done; } val = strtol(w.we_wordv[i], &endptr, 0); if (!endptr || *endptr != '\0' || val > UINT8_MAX) { - rl_printf("Invalid value at index %d\n", i); + bt_shell_printf("Invalid value at index %d\n", i); ad_clear_manufacturer(); goto done; } diff --git a/client/agent.c b/client/agent.c index dedd6abe6..e8ca4dd19 100644 --- a/client/agent.c +++ b/client/agent.c @@ -27,10 +27,12 @@ #include <stdio.h> #include <stdlib.h> -#include <readline/readline.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include "src/shared/shell.h" #include "gdbus/gdbus.h" -#include "display.h" #include "agent.h" #define AGENT_PATH "/org/bluez/agent" @@ -47,7 +49,7 @@ static void agent_release_prompt(void) if (!pending_message) return; - rl_release_prompt(""); + bt_shell_release_prompt(""); } dbus_bool_t agent_completion(void) @@ -114,7 +116,7 @@ static void agent_release(DBusConnection *conn) static DBusMessage *release_agent(DBusConnection *conn, DBusMessage *msg, void *user_data) { - rl_printf("Agent released\n"); + bt_shell_printf("Agent released\n"); agent_release(conn); @@ -126,12 +128,13 @@ static DBusMessage *request_pincode(DBusConnection *conn, { const char *device; - rl_printf("Request PIN code\n"); + bt_shell_printf("Request PIN code\n"); dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_INVALID); - rl_prompt_input("agent", "Enter PIN code:", pincode_response, conn); + bt_shell_prompt_input("agent", "Enter PIN code:", pincode_response, + conn); pending_message = dbus_message_ref(msg); @@ -147,7 +150,7 @@ static DBusMessage *display_pincode(DBusConnection *conn, dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_STRING, &pincode, DBUS_TYPE_INVALID); - rl_printf(AGENT_PROMPT "PIN code: %s\n", pincode); + bt_shell_printf(AGENT_PROMPT "PIN code: %s\n", pincode); return dbus_message_new_method_return(msg); } @@ -157,13 +160,13 @@ static DBusMessage *request_passkey(DBusConnection *conn, { const char *device; - rl_printf("Request passkey\n"); + bt_shell_printf("Request passkey\n"); dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_INVALID); - rl_prompt_input("agent", "Enter passkey (number in 0-999999):", - passkey_response, conn); + bt_shell_prompt_input("agent", "Enter passkey (number in 0-999999):", + passkey_response, conn); pending_message = dbus_message_ref(msg); @@ -188,7 +191,7 @@ static DBusMessage *display_passkey(DBusConnection *conn, if (entered > strlen(passkey_full)) entered = strlen(passkey_full); - rl_printf(AGENT_PROMPT "Passkey: " + bt_shell_printf(AGENT_PROMPT "Passkey: " COLOR_BOLDGRAY "%.*s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, entered, passkey_full, passkey_full + entered); @@ -202,13 +205,13 @@ static DBusMessage *request_confirmation(DBusConnection *conn, dbus_uint32_t passkey; char *str; - rl_printf("Request confirmation\n"); + bt_shell_printf("Request confirmation\n"); dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID); str = g_strdup_printf("Confirm passkey %06u (yes/no):", passkey); - rl_prompt_input("agent", str, confirm_response, conn); + bt_shell_prompt_input("agent", str, confirm_response, conn); g_free(str); pending_message = dbus_message_ref(msg); @@ -221,13 +224,13 @@ static DBusMessage *request_authorization(DBusConnection *conn, { const char *device; - rl_printf("Request authorization\n"); + bt_shell_printf("Request authorization\n"); dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_INVALID); - rl_prompt_input("agent", "Accept pairing (yes/no):", confirm_response, - conn); + bt_shell_prompt_input("agent", "Accept pairing (yes/no):", + confirm_response, conn); pending_message = dbus_message_ref(msg); @@ -240,13 +243,13 @@ static DBusMessage *authorize_service(DBusConnection *conn, const char *device, *uuid; char *str; - rl_printf("Authorize service\n"); + bt_shell_printf("Authorize service\n"); dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, DBUS_TYPE_STRING, &uuid, DBUS_TYPE_INVALID); str = g_strdup_printf("Authorize service %s (yes/no):", uuid); - rl_prompt_input("agent", str, confirm_response, conn); + bt_shell_prompt_input("agent", str, confirm_response, conn); g_free(str); pending_message = dbus_message_ref(msg); @@ -257,7 +260,7 @@ static DBusMessage *authorize_service(DBusConnection *conn, static DBusMessage *cancel_request(DBusConnection *conn, DBusMessage *msg, void *user_data) { - rl_printf("Request canceled\n"); + bt_shell_printf("Request canceled\n"); agent_release_prompt(); dbus_message_unref(pending_message); @@ -312,14 +315,14 @@ static void register_agent_reply(DBusMessage *message, void *user_data) if (dbus_set_error_from_message(&error, message) == FALSE) { agent_registered = TRUE; - rl_printf("Agent registered\n"); + bt_shell_printf("Agent registered\n"); } else { - rl_printf("Failed to register agent: %s\n", error.name); + bt_shell_printf("Failed to register agent: %s\n", error.name); dbus_error_free(&error); if (g_dbus_unregister_interface(conn, AGENT_PATH, AGENT_INTERFACE) == FALSE) - rl_printf("Failed to unregister agent object\n"); + bt_shell_printf("Failed to unregister agent object\n"); } } @@ -328,7 +331,7 @@ void agent_register(DBusConnection *conn, GDBusProxy *manager, { if (agent_registered == TRUE) { - rl_printf("Agent is already registered\n"); + bt_shell_printf("Agent is already registered\n"); return; } @@ -337,7 +340,7 @@ void agent_register(DBusConnection *conn, GDBusProxy *manager, if (g_dbus_register_interface(conn, AGENT_PATH, AGENT_INTERFACE, methods, NULL, NULL, NULL, NULL) == FALSE) { - rl_printf("Failed to register agent object\n"); + bt_shell_printf("Failed to register agent object\n"); return; } @@ -345,7 +348,7 @@ void agent_register(DBusConnection *conn, GDBusProxy *manager, register_agent_setup, register_agent_reply, conn, NULL) == FALSE) { - rl_printf("Failed to call register agent method\n"); + bt_shell_printf("Failed to call register agent method\n"); return; } @@ -367,10 +370,10 @@ static void unregister_agent_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == FALSE) { - rl_printf("Agent unregistered\n"); + bt_shell_printf("Agent unregistered\n"); agent_release(conn); } else { - rl_printf("Failed to unregister agent: %s\n", error.name); + bt_shell_printf("Failed to unregister agent: %s\n", error.name); dbus_error_free(&error); } } @@ -378,12 +381,12 @@ static void unregister_agent_reply(DBusMessage *message, void *user_data) void agent_unregister(DBusConnection *conn, GDBusProxy *manager) { if (agent_registered == FALSE) { - rl_printf("No agent is registered\n"); + bt_shell_printf("No agent is registered\n"); return; } if (!manager) { - rl_printf("Agent unregistered\n"); + bt_shell_printf("Agent unregistered\n"); agent_release(conn); return; } @@ -392,7 +395,7 @@ void agent_unregister(DBusConnection *conn, GDBusProxy *manager) unregister_agent_setup, unregister_agent_reply, conn, NULL) == FALSE) { - rl_printf("Failed to call unregister agent method\n"); + bt_shell_printf("Failed to call unregister agent method\n"); return; } } @@ -411,18 +414,19 @@ static void request_default_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to request default agent: %s\n", error.name); + bt_shell_printf("Failed to request default agent: %s\n", + error.name); dbus_error_free(&error); return; } - rl_printf("Default agent request successful\n"); + bt_shell_printf("Default agent request successful\n"); } void agent_default(DBusConnection *conn, GDBusProxy *manager) { if (agent_registered == FALSE) { - rl_printf("No agent is registered\n"); + bt_shell_printf("No agent is registered\n"); return; } @@ -430,7 +434,7 @@ void agent_default(DBusConnection *conn, GDBusProxy *manager) request_default_setup, request_default_reply, NULL, NULL) == FALSE) { - rl_printf("Failed to call request default agent method\n"); + bt_shell_printf("Failed to call RequestDefaultAgent method\n"); return; } } diff --git a/client/gatt.c b/client/gatt.c index 93aec92e7..ed0e71a20 100644 --- a/client/gatt.c +++ b/client/gatt.c @@ -33,16 +33,15 @@ #include <sys/uio.h> #include <wordexp.h> #include <fcntl.h> +#include <string.h> -#include <readline/readline.h> -#include <readline/history.h> #include <glib.h> #include "src/shared/queue.h" #include "src/shared/io.h" +#include "src/shared/shell.h" #include "gdbus/gdbus.h" #include "monitor/uuid.h" -#include "display.h" #include "gatt.h" #define APP_PATH "/org/bluez/app" @@ -109,7 +108,7 @@ static void print_service(struct service *service, const char *description) text = uuidstr_to_str(service->uuid); if (!text) - rl_printf("%s%s%s%s Service\n\t%s\n\t%s\n", + bt_shell_printf("%s%s%s%s Service\n\t%s\n\t%s\n", description ? "[" : "", description ? : "", description ? "] " : "", @@ -117,7 +116,7 @@ static void print_service(struct service *service, const char *description) "Secondary", service->path, service->uuid); else - rl_printf("%s%s%s%s Service\n\t%s\n\t%s\n\t%s\n", + bt_shell_printf("%s%s%s%s Service\n\t%s\n\t%s\n\t%s\n", description ? "[" : "", description ? : "", description ? "] " : "", @@ -176,13 +175,13 @@ static void print_chrc(struct chrc *chrc, const char *description) text = uuidstr_to_str(chrc->uuid); if (!text) - rl_printf("%s%s%sCharacteristic\n\t%s\n\t%s\n", + bt_shell_printf("%s%s%sCharacteristic\n\t%s\n\t%s\n", description ? "[" : "", description ? : "", description ? "] " : "", chrc->path, chrc->uuid); else - rl_printf("%s%s%sCharacteristic\n\t%s\n\t%s\n\t%s\n", + bt_shell_printf("%s%s%sCharacteristic\n\t%s\n\t%s\n\t%s\n", description ? "[" : "", description ? : "", description ? "] " : "", @@ -275,13 +274,13 @@ static void print_desc(struct desc *desc, const char *description) text = uuidstr_to_str(desc->uuid); if (!text) - rl_printf("%s%s%sDescriptor\n\t%s\n\t%s\n", + bt_shell_printf("%s%s%sDescriptor\n\t%s\n\t%s\n", description ? "[" : "", description ? : "", description ? "] " : "", desc->path, desc->uuid); else - rl_printf("%s%s%sDescriptor\n\t%s\n\t%s\n\t%s\n", + bt_shell_printf("%s%s%sDescriptor\n\t%s\n\t%s\n\t%s\n", description ? "[" : "", description ? : "", description ? "] " : "", @@ -523,7 +522,7 @@ static void read_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to read: %s\n", error.name); + bt_shell_printf("Failed to read: %s\n", error.name); dbus_error_free(&error); return; } @@ -531,7 +530,7 @@ static void read_reply(DBusMessage *message, void *user_data) dbus_message_iter_init(message, &iter); if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { - rl_printf("Invalid response to read\n"); + bt_shell_printf("Invalid response to read\n"); return; } @@ -539,11 +538,11 @@ static void read_reply(DBusMessage *message, void *user_data) dbus_message_iter_get_fixed_array(&array, &value, &len); if (len < 0) { - rl_printf("Unable to parse value\n"); + bt_shell_printf("Unable to parse value\n"); return; } - rl_hexdump(value, len); + bt_shell_hexdump(value, len); } static void read_setup(DBusMessageIter *iter, void *user_data) @@ -564,11 +563,11 @@ static void read_attribute(GDBusProxy *proxy) { if (g_dbus_proxy_method_call(proxy, "ReadValue", read_setup, read_reply, NULL, NULL) == FALSE) { - rl_printf("Failed to read\n"); + bt_shell_printf("Failed to read\n"); return; } - rl_printf("Attempting to read %s\n", g_dbus_proxy_get_path(proxy)); + bt_shell_printf("Attempting to read %s\n", g_dbus_proxy_get_path(proxy)); } void gatt_read_attribute(GDBusProxy *proxy) @@ -582,7 +581,7 @@ void gatt_read_attribute(GDBusProxy *proxy) return; } - rl_printf("Unable to read attribute %s\n", + bt_shell_printf("Unable to read attribute %s\n", g_dbus_proxy_get_path(proxy)); } @@ -593,7 +592,7 @@ static void write_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to write: %s\n", error.name); + bt_shell_printf("Failed to write: %s\n", error.name); dbus_error_free(&error); return; } @@ -634,13 +633,13 @@ static void write_attribute(GDBusProxy *proxy, char *arg) continue; if (i >= G_N_ELEMENTS(value)) { - rl_printf("Too much data\n"); + bt_shell_printf("Too much data\n"); return; } val = strtol(entry, &endptr, 0); if (!endptr || *endptr != '\0' || val > UINT8_MAX) { - rl_printf("Invalid value at index %d\n", i); + bt_shell_printf("Invalid value at index %d\n", i); return; } @@ -652,10 +651,10 @@ static void write_attribute(GDBusProxy *proxy, char *arg) /* Write using the fd if it has been acquired and fit the MTU */ if (proxy == write_io.proxy && (write_io.io && write_io.mtu >= i)) { - rl_printf("Attempting to write fd %d\n", + bt_shell_printf("Attempting to write fd %d\n", io_get_fd(write_io.io)); if (io_send(write_io.io, &iov, 1) < 0) { - rl_printf("Failed to write: %s", strerror(errno)); + bt_shell_printf("Failed to write: %s", strerror(errno)); return; } return; @@ -663,11 +662,11 @@ static void write_attribute(GDBusProxy *proxy, char *arg) if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup, write_reply, &iov, NULL) == FALSE) { - rl_printf("Failed to write\n"); + bt_shell_printf("Failed to write\n"); return; } - rl_printf("Attempting to write %s\n", g_dbus_proxy_get_path(proxy)); + bt_shell_printf("Attempting to write %s\n", g_dbus_proxy_get_path(proxy)); } void gatt_write_attribute(GDBusProxy *proxy, const char *arg) @@ -681,7 +680,7 @@ void gatt_write_attribute(GDBusProxy *proxy, const char *arg) return; } - rl_printf("Unable to write attribute %s\n", + bt_shell_printf("Unable to write attribute %s\n", g_dbus_proxy_get_path(proxy)); } @@ -700,13 +699,13 @@ static bool pipe_read(struct io *io, void *user_data) return false; if (chrc) - rl_printf("[" COLORED_CHG "] Attribute %s written:\n", + bt_shell_printf("[" COLORED_CHG "] Attribute %s written:\n", chrc->path); else - rl_printf("[" COLORED_CHG "] %s Notification:\n", + bt_shell_printf("[" COLORED_CHG "] %s Notification:\n", g_dbus_proxy_get_path(notify_io.proxy)); - rl_hexdump(buf, bytes_read); + bt_shell_hexdump(buf, bytes_read); return true; } @@ -716,7 +715,7 @@ static bool pipe_hup(struct io *io, void *user_data) struct chrc *chrc = user_data; if (chrc) { - rl_printf("Attribute %s Write pipe closed\n", chrc->path); + bt_shell_printf("Attribute %s Write pipe closed\n", chrc->path); if (chrc->write_io) { io_destroy(chrc->write_io); chrc->write_io = NULL; @@ -724,7 +723,7 @@ static bool pipe_hup(struct io *io, void *user_data) return false; } - rl_printf("%s closed\n", io == notify_io.io ? "Notify" : "Write"); + bt_shell_printf("%s closed\n", io == notify_io.io ? "Notify" : "Write"); if (io == notify_io.io) notify_io_destroy(); @@ -757,7 +756,7 @@ static void acquire_write_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to acquire write: %s\n", error.name); + bt_shell_printf("Failed to acquire write: %s\n", error.name); dbus_error_free(&error); write_io.proxy = NULL; return; @@ -769,11 +768,11 @@ static void acquire_write_reply(DBusMessage *message, void *user_data) if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &write_io.mtu, DBUS_TYPE_INVALID) == false)) { - rl_printf("Invalid AcquireWrite response\n"); + bt_shell_printf("Invalid AcquireWrite response\n"); return; } - rl_printf("AcquireWrite success: fd %d MTU %u\n", fd, write_io.mtu); + bt_shell_printf("AcquireWrite success: fd %d MTU %u\n", fd, write_io.mtu); write_io.io = pipe_io_new(fd, NULL); } @@ -798,14 +797,14 @@ void gatt_acquire_write(GDBusProxy *proxy, const char *arg) iface = g_dbus_proxy_get_interface(proxy); if (strcmp(iface, "org.bluez.GattCharacteristic1")) { - rl_printf("Unable to acquire write: %s not a characteristic\n", + bt_shell_printf("Unable to acquire write: %s not a characteristic\n", g_dbus_proxy_get_path(proxy)); return; } if (g_dbus_proxy_method_call(proxy, "AcquireWrite", acquire_setup, acquire_write_reply, NULL, NULL) == FALSE) { - rl_printf("Failed to AcquireWrite\n"); + bt_shell_printf("Failed to AcquireWrite\n"); return; } @@ -815,7 +814,7 @@ void gatt_acquire_write(GDBusProxy *proxy, const char *arg) void gatt_release_write(GDBusProxy *proxy, const char *arg) { if (proxy != write_io.proxy || !write_io.io) { - rl_printf("Write not acquired\n"); + bt_shell_printf("Write not acquired\n"); return; } @@ -830,7 +829,7 @@ static void acquire_notify_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to acquire notify: %s\n", error.name); + bt_shell_printf("Failed to acquire notify: %s\n", error.name); dbus_error_free(&error); write_io.proxy = NULL; return; @@ -846,11 +845,11 @@ static void acquire_notify_reply(DBusMessage *message, void *user_data) if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, ¬ify_io.mtu, DBUS_TYPE_INVALID) == false)) { - rl_printf("Invalid AcquireNotify response\n"); + bt_shell_printf("Invalid AcquireNotify response\n"); return; } - rl_printf("AcquireNotify success: fd %d MTU %u\n", fd, notify_io.mtu); + bt_shell_printf("AcquireNotify success: fd %d MTU %u\n", fd, notify_io.mtu); notify_io.io = pipe_io_new(fd, NULL); } @@ -861,14 +860,14 @@ void gatt_acquire_notify(GDBusProxy *proxy, const char *arg) iface = g_dbus_proxy_get_interface(proxy); if (strcmp(iface, "org.bluez.GattCharacteristic1")) { - rl_printf("Unable to acquire notify: %s not a characteristic\n", + bt_shell_printf("Unable to acquire notify: %s not a characteristic\n", g_dbus_proxy_get_path(proxy)); return; } if (g_dbus_proxy_method_call(proxy, "AcquireNotify", acquire_setup, acquire_notify_reply, NULL, NULL) == FALSE) { - rl_printf("Failed to AcquireNotify\n"); + bt_shell_printf("Failed to AcquireNotify\n"); return; } @@ -878,7 +877,7 @@ void gatt_acquire_notify(GDBusProxy *proxy, const char *arg) void gatt_release_notify(GDBusProxy *proxy, const char *arg) { if (proxy != notify_io.proxy || !notify_io.io) { - rl_printf("Notify not acquired\n"); + bt_shell_printf("Notify not acquired\n"); return; } @@ -893,13 +892,13 @@ static void notify_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to %s notify: %s\n", + bt_shell_printf("Failed to %s notify: %s\n", enable ? "start" : "stop", error.name); dbus_error_free(&error); return; } - rl_printf("Notify %s\n", enable == TRUE ? "started" : "stopped"); + bt_shell_printf("Notify %s\n", enable == TRUE ? "started" : "stopped"); } static void notify_attribute(GDBusProxy *proxy, bool enable) @@ -913,7 +912,7 @@ static void notify_attribute(GDBusProxy *proxy, bool enable) if (g_dbus_proxy_method_call(proxy, method, NULL, notify_reply, GUINT_TO_POINTER(enable), NULL) == FALSE) { - rl_printf("Failed to %s notify\n", enable ? "start" : "stop"); + bt_shell_printf("Failed to %s notify\n", enable ? "start" : "stop"); return; } } @@ -928,7 +927,7 @@ void gatt_notify_attribute(GDBusProxy *proxy, bool enable) return; } - rl_printf("Unable to notify attribute %s\n", + bt_shell_printf("Unable to notify attribute %s\n", g_dbus_proxy_get_path(proxy)); } @@ -956,12 +955,12 @@ static void register_app_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to register application: %s\n", error.name); + bt_shell_printf("Failed to register application: %s\n", error.name); dbus_error_free(&error); return; } - rl_printf("Application registered\n"); + bt_shell_printf("Application registered\n"); } void gatt_add_manager(GDBusProxy *proxy) @@ -1026,7 +1025,7 @@ void gatt_register_app(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) l = g_list_find_custom(managers, proxy, match_proxy); if (!l) { - rl_printf("Unable to find GattManager proxy\n"); + bt_shell_printf("Unable to find GattManager proxy\n"); return; } @@ -1038,7 +1037,7 @@ void gatt_register_app(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) PROFILE_INTERFACE, methods, NULL, properties, NULL, NULL) == FALSE) { - rl_printf("Failed to register application object\n"); + bt_shell_printf("Failed to register application object\n"); return; } } @@ -1047,7 +1046,7 @@ void gatt_register_app(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) register_app_setup, register_app_reply, w, NULL) == FALSE) { - rl_printf("Failed register application\n"); + bt_shell_printf("Failed register application\n"); g_dbus_unregister_interface(conn, APP_PATH, PROFILE_INTERFACE); return; } @@ -1061,12 +1060,12 @@ static void unregister_app_reply(DBusMessage *message, void *user_data) dbus_error_init(&error); if (dbus_set_error_from_message(&error, message) == TRUE) { - rl_printf("Failed to unregister application: %s\n", error.name); + bt_shell_printf("Failed to unregister application: %s\n", error.name); dbus_error_free(&error); return; } - rl_printf("Application unregistered\n"); + bt_shell_printf("Application unregistered\n"); if (!uuids) return; @@ -1090,7 +1089,7 @@ void gatt_unregister_app(DBusConnection *conn, GDBusProxy *proxy) l = g_list_find_custom(managers, proxy, match_proxy); if (!l) { - rl_printf("Unable to find GattManager proxy\n"); + bt_shell_printf("Unable to find GattManager proxy\n"); return; } @@ -1098,7 +1097,7 @@ void gatt_unregister_app(DBusConnection *conn, GDBusProxy *proxy) unregister_app_setup, unregister_app_reply, conn, NULL) == FALSE) { - rl_printf("Failed unregister profile\n"); + bt_shell_printf("Failed unregister profile\n"); return; } } @@ -1194,7 +1193,7 @@ static void service_set_primary(const char *input, void *user_data) else if (!strcmp(input, "no")) { service->primary = false; } else { - rl_printf("Invalid option: %s\n", input); + bt_shell_printf("Invalid option: %s\n", input); local_services = g_list_remove(local_services, service); print_service(service, COLORED_DEL); g_dbus_unregister_interface(service->conn, service->path, @@ -1218,7 +1217,7 @@ void gatt_register_service(DBusConnection *conn, GDBusProxy *proxy, SERVICE_INTERFACE, NULL, NULL, service_properties, service, service_free) == FALSE) { - rl_printf("Failed to register service object\n"); + bt_shell_printf("Failed to register service object\n"); service_free(service); return; } @@ -1227,7 +1226,7 @@ void gatt_register_service(DBusConnection *conn, GDBusProxy *proxy, local_services = g_list_append(local_services, service); - rl_prompt_input(service->path, "Primary (yes/no):", service_set_primary, + bt_shell_prompt_input(service->path, "Primary (yes/no):", service_set_primary, service); } @@ -1257,7 +1256,7 @@ void gatt_unregister_service(DBusConnection *conn, GDBusProxy *proxy, service = service_find(w->we_wordv[0]); if (!service) { - rl_printf("Failed to unregister service object\n"); + bt_shell_printf("Failed to unregister service object\n"); return; } @@ -1455,7 +1454,7 @@ static DBusMessage *chrc_write_value(DBusConnection *conn, DBusMessage *msg, "org.bluez.Error.InvalidArguments", NULL); - rl_printf("[" COLORED_CHG "] Attribute %s written" , chrc->path); + bt_shell_printf("[" COLORED_CHG "] Attribute %s written" , chrc->path); g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, "Value"); @@ -1530,7 +1529,7 @@ static DBusMessage *chrc_create_pipe(struct chrc *chrc, DBusMessage *msg) else chrc->notify_io = io; - rl_printf("[" COLORED_CHG "] Attribute %s %s pipe acquired\n", + bt_shell_printf("[" COLORED_CHG "] Attribute %s %s pipe acquired\n", chrc->path, dir ? "Write" : "Notify"); return reply; @@ -1601,7 +1600,7 @@ static DBusMessage *chrc_start_notify(DBusConnection *conn, DBusMessage *msg, return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); chrc->notifying = true; - rl_printf("[" COLORED_CHG "] Attribute %s notifications enabled", + bt_shell_printf("[" COLORED_CHG "] Attribute %s notifications enabled", chrc->path); g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, "Notifying"); @@ -1618,7 +1617,7 @@ static DBusMessage *chrc_stop_notify(DBusConnection *conn, DBusMessage *msg, return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); chrc->notifying = false; - rl_printf("[" COLORED_CHG "] Attribute %s notifications disabled", + bt_shell_printf("[" COLORED_CHG "] Attribute %s notifications disabled", chrc->path); g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, "Notifying"); @@ -1631,7 +1630,7 @@ static DBusMessage *chrc_confirm(DBusConnection *conn, DBusMessage *msg, { struct chrc *chrc = user_data; - rl_printf("Attribute %s indication confirm received", chrc->path); + bt_shell_printf("Attribute %s indication confirm received", chrc->path); return dbus_message_new_method_return(msg); } @@ -1667,13 +1666,13 @@ static uint8_t *str2bytearray(char *arg, int *val_len) continue; if (i >= G_N_ELEMENTS(value)) { - rl_printf("Too much data\n"); + bt_shell_printf("Too much data\n"); return NULL; } val = strtol(entry, &endptr, 0); if (!endptr || *endptr != '\0' || val > UINT8_MAX) { - rl_printf("Invalid value at index %d\n", i); + bt_shell_printf("Invalid value at index %d\n", i); return NULL; } @@ -1700,7 +1699,7 @@ void gatt_register_chrc(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) struct chrc *chrc; if (!local_services) { - rl_printf("No service registered\n"); + bt_shell_printf("No service registered\n"); return; } @@ -1715,7 +1714,7 @@ void gatt_register_chrc(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) if (g_dbus_register_interface(conn, chrc->path, CHRC_INTERFACE, chrc_methods, NULL, chrc_properties, chrc, chrc_free) == FALSE) { - rl_printf("Failed to register characteristic object\n"); + bt_shell_printf("Failed to register characteristic object\n"); chrc_free(chrc); return; } @@ -1724,7 +1723,7 @@ void gatt_register_chrc(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) print_chrc(chrc, COLORED_NEW); - rl_prompt_input(chrc->path, "Enter value:", chrc_set_value, chrc); + bt_shell_prompt_input(chrc->path, "Enter value:", chrc_set_value, chrc); } static struct chrc *chrc_find(const char *pattern) @@ -1759,7 +1758,7 @@ void gatt_unregister_chrc(DBusConnection *conn, GDBusProxy *proxy, chrc = chrc_find(w->we_wordv[0]); if (!chrc) { - rl_printf("Failed to unregister characteristic object\n"); + bt_shell_printf("Failed to unregister characteristic object\n"); return; } @@ -1789,7 +1788,7 @@ static DBusMessage *desc_write_value(DBusConnection *conn, DBusMessage *msg, "org.bluez.Error.InvalidArguments", NULL); - rl_printf("[" COLORED_CHG "] Attribute %s written" , desc->path); + bt_shell_printf("[" COLORED_CHG "] Attribute %s written" , desc->path); g_dbus_emit_property_changed(conn, desc->path, CHRC_INTERFACE, "Value"); @@ -1886,14 +1885,14 @@ void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) struct desc *desc; if (!local_services) { - rl_printf("No service registered\n"); + bt_shell_printf("No service registered\n"); return; } service = g_list_last(local_services)->data; if (!service->chrcs) { - rl_printf("No characteristic registered\n"); + bt_shell_printf("No characteristic registered\n"); return; } @@ -1906,7 +1905,7 @@ void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) if (g_dbus_register_interface(conn, desc->path, DESC_INTERFACE, desc_methods, NULL, desc_properties, desc, desc_free) == FALSE) { - rl_printf("Failed to register descriptor object\n"); + bt_shell_printf("Failed to register descriptor object\n"); desc_free(desc); return; } @@ -1915,7 +1914,7 @@ void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy, wordexp_t *w) print_desc(desc, COLORED_NEW); - rl_prompt_input(desc->path, "Enter value:", desc_set_value, desc); + bt_shell_prompt_input(desc->path, "Enter value:", desc_set_value, desc); } static struct desc *desc_find(const char *pattern) @@ -1955,7 +1954,7 @@ void gatt_unregister_desc(DBusConnection *conn, GDBusProxy *proxy, desc = desc_find(w->we_wordv[0]); if (!desc) { - rl_printf("Failed to unregister descriptor object\n"); + bt_shell_printf("Failed to unregister descriptor object\n"); return; } diff --git a/client/main.c b/client/main.c index 37a411ba9..bbdd55a98 100644 --- a/client/main.c +++ b/client/main.c @@ -30,14 +30,11 @@ #include <unistd.h> #include <stdlib.h> #include <stdbool.h> -#include <signal.h> -#include <sys/signalfd.h> #include <wordexp.h> -#include <readline/readline.h> -#include <readline/history.h> #include <glib.h> +#include "src/shared/shell.h" #include "src/shared/util.h" #include "gdbus/gdbus.h" #include "monitor/uuid.h" @@ -54,7 +51,6 @@ #define PROMPT_ON COLOR_BLUE "[bluetooth]" COLOR_OFF "# " #define PROMPT_OFF "Waiting to connect to bluetoothd..." -static GMainLoop *main_loop; static DBusConnection *dbus_conn; static GDBusProxy *agent_manager; @@ -71,8 +67,6 @@ static GDBusProxy *default_dev; static GDBusProxy *default_attr; static GList *ctrl_list; -static guint input = 0; - static const char *mode_arguments[] = { "on", "off", @@ -103,57 +97,21 @@ static void proxy_leak(gpointer data) printf("Leaking proxy %p\n", data); } -static gboolean input_handler(GIOChannel *channel, GIOCondition condition, - gpointer user_data) -{ - if (condition & G_IO_IN) { - rl_callback_read_char(); - return TRUE; - } - - if (condition & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { - g_main_loop_quit(main_loop); - return FALSE; - } - - return TRUE; -} - -static guint setup_standard_input(void) +static void setup_standard_input(void) { - GIOChannel *channel; - guint source; - - channel = g_io_channel_unix_new(fileno(stdin)); - - source = g_io_add_watch(channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - input_handler, NULL); - - g_io_channel_unref(channel); - - return source; + bt_shell_attach(fileno(stdin)); } static void connect_handler(DBusConnection *connection, void *user_data) { - rl_set_prompt(PROMPT_ON); - printf("\r"); - rl_on_new_line(); - rl_redisplay(); + bt_shell_set_prompt(PROMPT_ON); } static void disconnect_handler(DBusConnection *connection, void *user_data) { - if (input > 0) { - g_source_remove(input); - input = 0; - } + bt_shell_detach(); - rl_set_prompt(PROMPT_OFF); - printf("\r"); - rl_on_new_line(); - rl_redisplay(); + bt_shell_set_prompt(PROMPT_OFF); g_list_free_full(ctrl_list, proxy_leak); ctrl_list = NULL; @@ -493,9 +451,7 @@ static void set_default_device(GDBusProxy *proxy, const char *attribute) attribute ? attribute + strlen(path) : ""); done: - rl_set_prompt(desc ? desc : PROMPT_ON); - printf("\r"); - rl_on_new_line(); + bt_shell_set_prompt(desc ? desc : PROMPT_ON); g_free(desc); } @@ -2160,18 +2116,6 @@ done: wordfree(&w); } -static void cmd_version(const char *arg) -{ - rl_printf("Version %s\n", VERSION); -} - -static void cmd_quit(const char *arg) -{ - g_main_loop_quit(main_loop); -} - -static void cmd_help(const char *arg); - static char *generic_generator(const char *text, int state, GList *source, const char *property) { @@ -2414,14 +2358,7 @@ static void cmd_set_advertise_timeout(const char *arg) ad_advertise_timeout(dbus_conn, value); } -static const struct { - const char *cmd; - const char *arg; - void (*func) (const char *arg); - const char *desc; - char * (*gen) (const char *text, int state); - void (*disp) (char **matches, int num_matches, int max_length); -} cmd_table[] = { +static const struct bt_shell_menu_entry cmd_table[] = { { "list", NULL, cmd_list, "List available controllers" }, { "show", "[ctrl]", cmd_show, "Controller information", ctrl_generator }, @@ -2545,230 +2482,9 @@ static const struct { { "unregister-descriptor", "<UUID/object>", cmd_unregister_descriptor, "Unregister application descriptor" }, - { "version", NULL, cmd_version, "Display version" }, - { "quit", NULL, cmd_quit, "Quit program" }, - { "exit", NULL, cmd_quit, "Quit program" }, - { "help", NULL, cmd_help, - "Display help about this program" }, { } }; -static char *cmd_generator(const char *text, int state) -{ - static int index, len; - const char *cmd; - - if (!state) { - index = 0; - len = strlen(text); - } - - while ((cmd = cmd_table[index].cmd)) { - index++; - - if (!strncmp(cmd, text, len)) - return strdup(cmd); - } - - return NULL; -} - -static char **cmd_completion(const char *text, int start, int end) -{ - char **matches = NULL; - - if (agent_completion() == TRUE) { - rl_attempted_completion_over = 1; - return NULL; - } - - if (start > 0) { - int i; - char *input_cmd; - - input_cmd = g_strndup(rl_line_buffer, start -1); - for (i = 0; cmd_table[i].cmd; i++) { - if (strcmp(cmd_table[i].cmd, input_cmd)) - continue; - - if (!cmd_table[i].gen) - continue; - - rl_completion_display_matches_hook = cmd_table[i].disp; - matches = rl_completion_matches(text, cmd_table[i].gen); - break; - } - g_free(input_cmd); - } else { - rl_completion_display_matches_hook = NULL; - matches = rl_completion_matches(text, cmd_generator); - } - - if (!matches) - rl_attempted_completion_over = 1; - - return matches; -} - -static void rl_handler(char *input) -{ - char *cmd, *arg; - int i; - - if (!input) { - rl_insert_text("quit"); - rl_redisplay(); - rl_crlf(); - g_main_loop_quit(main_loop); - return; - } - - if (!strlen(input)) - goto done; - - if (!rl_release_prompt(input)) - goto done; - - if (history_search(input, -1)) - add_history(input); - - cmd = strtok_r(input, " ", &arg); - if (!cmd) - goto done; - - if (arg) { - int len = strlen(arg); - if (len > 0 && arg[len - 1] == ' ') - arg[len - 1] = '\0'; - } - - for (i = 0; cmd_table[i].cmd; i++) { - if (strcmp(cmd, cmd_table[i].cmd)) - continue; - - if (cmd_table[i].func) { - cmd_table[i].func(arg); - goto done; - } - } - - printf("Invalid command\n"); -done: - free(input); -} - -static void cmd_help(const char *arg) -{ - int i; - - printf("Available commands:\n"); - - for (i = 0; cmd_table[i].cmd; i++) { - if ((int)strlen(cmd_table[i].arg? : "") <= - (int)(25 - strlen(cmd_table[i].cmd))) - printf(" %s %-*s %s\n", cmd_table[i].cmd, - (int)(25 - strlen(cmd_table[i].cmd)), - cmd_table[i].arg ? : "", - cmd_table[i].desc ? : ""); - else - printf(" %s %-s\n" " %s %-25s %s\n", - cmd_table[i].cmd, - cmd_table[i].arg ? : "", - "", "", - cmd_table[i].desc ? : ""); - } -} - -static gboolean signal_handler(GIOChannel *channel, GIOCondition condition, - gpointer user_data) -{ - static bool terminated = false; - struct signalfd_siginfo si; - ssize_t result; - int fd; - - if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { - g_main_loop_quit(main_loop); - return FALSE; - } - - fd = g_io_channel_unix_get_fd(channel); - - result = read(fd, &si, sizeof(si)); - if (result != sizeof(si)) - return FALSE; - - switch (si.ssi_signo) { - case SIGINT: - if (input) { - rl_replace_line("", 0); - rl_crlf(); - rl_on_new_line(); - rl_redisplay(); - break; - } - - /* - * If input was not yet setup up that means signal was received - * while daemon was not yet running. Since user is not able - * to terminate client by CTRL-D or typing exit treat this as - * exit and fall through. - */ - - /* fall through */ - case SIGTERM: - if (!terminated) { - rl_replace_line("", 0); - rl_crlf(); - g_main_loop_quit(main_loop); - } - - terminated = true; - break; - } - - return TRUE; -} - -static guint setup_signalfd(void) -{ - GIOChannel *channel; - guint source; - sigset_t mask; - int fd; - - sigemptyset(&mask); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - - if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { - perror("Failed to set signal mask"); - return 0; - } - - fd = signalfd(-1, &mask, 0); - if (fd < 0) { - perror("Failed to create signal descriptor"); - return 0; - } - - channel = g_io_channel_unix_new(fd); - - g_io_channel_set_close_on_unref(channel, TRUE); - g_io_channel_set_encoding(channel, NULL, NULL); - g_io_channel_set_buffered(channel, FALSE); - - source = g_io_add_watch(channel, - G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - signal_handler, NULL); - - g_io_channel_unref(channel); - - return source; -} - -static gboolean option_version = FALSE; - static gboolean parse_agent(const char *key, const char *value, gpointer user_data, GError **error) { @@ -2782,8 +2498,6 @@ static gboolean parse_agent(const char *key, const char *value, } static GOptionEntry options[] = { - { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, - "Show version information and exit" }, { "agent", 'a', 0, G_OPTION_ARG_CALLBACK, parse_agent, "Register agent handler", "CAPABILITY" }, { NULL }, @@ -2791,8 +2505,7 @@ static GOptionEntry options[] = { static void client_ready(GDBusClient *client, void *user_data) { - if (!input) - input = setup_standard_input(); + setup_standard_input(); } int main(int argc, char *argv[]) @@ -2800,7 +2513,6 @@ int main(int argc, char *argv[]) GOptionContext *context; GError *error = NULL; GDBusClient *client; - guint signal; auto_register_agent = g_strdup(""); @@ -2818,25 +2530,13 @@ int main(int argc, char *argv[]) g_option_context_free(context); - if (option_version == TRUE) { - printf("%s\n", VERSION); - exit(0); - } + bt_shell_init(&argc, &argv); + bt_shell_set_menu(cmd_table); + bt_shell_set_prompt(PROMPT_OFF); - main_loop = g_main_loop_new(NULL, FALSE); dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); g_dbus_attach_object_manager(dbus_conn); - setlinebuf(stdout); - rl_attempted_completion_function = cmd_completion; - - rl_erase_empty_line = 1; - rl_callback_handler_install(NULL, rl_handler); - - rl_set_prompt(PROMPT_OFF); - rl_redisplay(); - - signal = setup_signalfd(); client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); g_dbus_client_set_connect_watch(client, connect_handler, NULL); @@ -2848,18 +2548,11 @@ int main(int argc, char *argv[]) g_dbus_client_set_ready_watch(client, client_ready, NULL); - g_main_loop_run(main_loop); + bt_shell_run(); g_dbus_client_unref(client); - g_source_remove(signal); - if (input > 0) - g_source_remove(input); - - rl_message(""); - rl_callback_handler_remove(); dbus_connection_unref(dbus_conn); - g_main_loop_unref(main_loop); g_list_free_full(ctrl_list, proxy_leak); -- 2.13.6 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 3/6] shared/shell: Add submenu support 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 2/6] client: Make use of bt_shell Luiz Augusto von Dentz @ 2017-11-16 10:59 ` Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 4/6] client: Move advertise related commands to a submenu Luiz Augusto von Dentz ` (3 subsequent siblings) 5 siblings, 0 replies; 7+ messages in thread From: Luiz Augusto von Dentz @ 2017-11-16 10:59 UTC (permalink / raw) To: linux-bluetooth From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> This adds submenu support so application can have one extra level of menus to better organize the interface. Submenus will be show in the output of help command, to select a submenu: > menu <name> To move back to main menu: > back --- client/main.c | 8 ++- src/shared/shell.c | 168 +++++++++++++++++++++++++++++++++++++++++++++-------- src/shared/shell.h | 11 +++- 3 files changed, 159 insertions(+), 28 deletions(-) diff --git a/client/main.c b/client/main.c index bbdd55a98..cf04047cb 100644 --- a/client/main.c +++ b/client/main.c @@ -2358,7 +2358,9 @@ static void cmd_set_advertise_timeout(const char *arg) ad_advertise_timeout(dbus_conn, value); } -static const struct bt_shell_menu_entry cmd_table[] = { +static const struct bt_shell_menu main_menu = { + .name = "main", + .entries = { { "list", NULL, cmd_list, "List available controllers" }, { "show", "[ctrl]", cmd_show, "Controller information", ctrl_generator }, @@ -2482,7 +2484,7 @@ static const struct bt_shell_menu_entry cmd_table[] = { { "unregister-descriptor", "<UUID/object>", cmd_unregister_descriptor, "Unregister application descriptor" }, - { } + { } }, }; static gboolean parse_agent(const char *key, const char *value, @@ -2531,7 +2533,7 @@ int main(int argc, char *argv[]) g_option_context_free(context); bt_shell_init(&argc, &argv); - bt_shell_set_menu(cmd_table); + bt_shell_set_menu(&main_menu); bt_shell_set_prompt(PROMPT_OFF); dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); diff --git a/src/shared/shell.c b/src/shared/shell.c index 7db629bf1..8c29dd73f 100644 --- a/src/shared/shell.c +++ b/src/shared/shell.c @@ -48,6 +48,9 @@ #define print_menu(cmd, args, desc) \ printf(COLOR_HIGHLIGHT "%s %-*s " COLOR_OFF "%s\n", \ cmd, (int)(CMD_LENGTH - strlen(cmd)), args, desc) +#define print_submenu(cmd, desc) \ + printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \ + cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc) static GMainLoop *main_loop; static gboolean option_version = FALSE; @@ -59,8 +62,9 @@ static struct { bt_shell_prompt_input_func saved_func; void *saved_user_data; - const struct bt_shell_menu_entry *menu; - /* TODO: Add submenus support */ + const struct bt_shell_menu *menu; + const struct bt_shell_menu *main; + struct queue *submenus; } data; static void shell_print_menu(void); @@ -80,7 +84,82 @@ static void cmd_help(const char *arg) shell_print_menu(); } +static const struct bt_shell_menu *find_menu(const char *name) +{ + const struct queue_entry *entry; + + for (entry = queue_get_entries(data.submenus); entry; + entry = entry->next) { + struct bt_shell_menu *menu = entry->data; + + if (!strcmp(menu->name, name)) + return menu; + } + + return NULL; +} + +static char *menu_generator(const char *text, int state) +{ + static unsigned int index, len; + static struct queue_entry *entry; + + if (!state) { + index = 0; + len = strlen(text); + entry = (void *) queue_get_entries(data.submenus); + } + + for (; entry; entry = entry->next) { + struct bt_shell_menu *menu = entry->data; + + index++; + + if (!strncmp(menu->name, text, len)) { + entry = entry->next; + return strdup(menu->name); + } + } + + return NULL; +} + +static void cmd_menu(const char *arg) +{ + const struct bt_shell_menu *menu; + + if (!arg || !strlen(arg)) { + bt_shell_printf("Missing name argument\n"); + return; + } + + menu = find_menu(arg); + if (!menu) { + bt_shell_printf("Unable find menu with name: %s\n", arg); + return; + } + + bt_shell_set_menu(menu); + + shell_print_menu(); +} + +static void cmd_back(const char *arg) +{ + if (data.menu == data.main) { + bt_shell_printf("Already on main menu\n"); + return; + } + + bt_shell_set_menu(data.main); + + shell_print_menu(); +} + static const struct bt_shell_menu_entry default_menu[] = { + { "back", NULL, cmd_back, "Return to main menu" }, + { "menu", "<name>", cmd_menu, "Select submenu", + menu_generator }, { "version", NULL, cmd_version, "Display version" }, { "quit", NULL, cmd_quit, "Quit program" }, { "exit", NULL, cmd_quit, "Quit program" }, @@ -92,49 +171,74 @@ static const struct bt_shell_menu_entry default_menu[] = { static void shell_print_menu(void) { const struct bt_shell_menu_entry *entry; + const struct queue_entry *submenu; if (!data.menu) return; + print_text(COLOR_HIGHLIGHT, "Menu %s:", data.menu->name); print_text(COLOR_HIGHLIGHT, "Available commands:"); print_text(COLOR_HIGHLIGHT, "-------------------"); - for (entry = data.menu; entry->cmd; entry++) { + + if (data.menu == data.main) { + for (submenu = queue_get_entries(data.submenus); submenu; + submenu = submenu->next) { + struct bt_shell_menu *menu = submenu->data; + + print_submenu(menu->name, "Submenu"); + } + } + + for (entry = data.menu->entries; entry->cmd; entry++) { print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); } for (entry = default_menu; entry->cmd; entry++) { + /* Skip menu command if not on main menu */ + if (data.menu != data.main && !strcmp(entry->cmd, "menu")) + continue; + + /* Skip back command if on main menu */ + if (data.menu == data.main && !strcmp(entry->cmd, "back")) + continue; + print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); } } -static void shell_exec(const char *cmd, const char *arg) +static int menu_exec(const struct bt_shell_menu_entry *entry, + const char *cmd, const char *arg) { - const struct bt_shell_menu_entry *entry; - - if (!data.menu || !cmd) - return; - - for (entry = data.menu; entry->cmd; entry++) { + for (; entry->cmd; entry++) { if (strcmp(cmd, entry->cmd)) continue; - if (entry->func) { - entry->func(arg); - return; - } - } + /* Skip menu command if not on main menu */ + if (data.menu != data.main && !strcmp(entry->cmd, "menu")) + continue; - for (entry = default_menu; entry->cmd; entry++) { - if (strcmp(cmd, entry->cmd)) + /* Skip back command if on main menu */ + if (data.menu == data.main && !strcmp(entry->cmd, "back")) continue; if (entry->func) { entry->func(arg); - return; + return 0; } } - print_text(COLOR_HIGHLIGHT, "Invalid command"); + return -ENOENT; +} + +static void shell_exec(const char *cmd, const char *arg) +{ + if (!data.menu || !cmd) + return; + + if (menu_exec(default_menu, cmd, arg) < 0) { + if (menu_exec(data.menu->entries, cmd, arg) < 0) + print_text(COLOR_HIGHLIGHT, "Invalid command"); + } } void bt_shell_printf(const char *fmt, ...) @@ -308,7 +412,7 @@ static char *cmd_generator(const char *text, int state) if (state) return NULL; - entry = data.menu; + entry = data.menu->entries; index = 0; return cmd_generator(text, 1); @@ -319,7 +423,7 @@ static char **menu_completion(const struct bt_shell_menu_entry *entry, { char **matches = NULL; - for (entry = data.menu; entry->cmd; entry++) { + for (; entry->cmd; entry++) { if (strcmp(entry->cmd, input_cmd)) continue; @@ -347,7 +451,7 @@ static char **shell_completion(const char *text, int start, int end) input_cmd = strndup(rl_line_buffer, start - 1); matches = menu_completion(default_menu, text, input_cmd); if (!matches) - matches = menu_completion(data.menu, text, + matches = menu_completion(data.menu->entries, text, input_cmd); free(input_cmd); @@ -513,13 +617,29 @@ void bt_shell_run(void) rl_cleanup(); } -bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu) +bool bt_shell_set_menu(const struct bt_shell_menu *menu) { - if (data.menu || !menu) + if (!menu) return false; data.menu = menu; + if (!data.main) + data.main = menu; + + return true; +} + +bool bt_shell_add_submenu(const struct bt_shell_menu *menu) +{ + if (!menu) + return false; + + if (!data.submenus) + data.submenus = queue_new(); + + queue_push_tail(data.submenus, (void *) menu); + return true; } diff --git a/src/shared/shell.h b/src/shared/shell.h index 843335784..114219cdf 100644 --- a/src/shared/shell.h +++ b/src/shared/shell.h @@ -45,11 +45,20 @@ struct bt_shell_menu_entry { bt_shell_menu_disp_t disp; }; +struct bt_shell_menu { + const char *name; + const struct bt_shell_menu_entry entries[]; +}; + void bt_shell_init(int *argc, char ***argv); void bt_shell_run(void); -bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu); +bool bt_shell_set_menu(const struct bt_shell_menu *menu); + +bool bt_shell_add_submenu(const struct bt_shell_menu *menu); + +bool bt_shell_remove_submenu(const struct bt_shell_menu *menu); void bt_shell_set_prompt(const char *string); -- 2.13.6 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 4/6] client: Move advertise related commands to a submenu 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 2/6] client: Make use of bt_shell Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 3/6] shared/shell: Add submenu support Luiz Augusto von Dentz @ 2017-11-16 10:59 ` Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 5/6] client: Move scan " Luiz Augusto von Dentz ` (2 subsequent siblings) 5 siblings, 0 replies; 7+ messages in thread From: Luiz Augusto von Dentz @ 2017-11-16 10:59 UTC (permalink / raw) To: linux-bluetooth From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> All commands related to advertise settings are now under advertise submenu: > menu advertise Menu advertise: Available commands: ------------------- set-uuids [uuid1 uuid2 ...] Set advertise uuids set-service [uuid][data=[xx xx ...] Set advertise service data set-manufacturer [id][data=[xx xx ...] Set advertise manufacturer data set-tx-power <on/off> Enable/disable TX power to be advertised set-name <on/off/name> Enable/disable local name to be advertised set-appearance <value> Set custom appearance to be advertised set-duration <seconds> Set advertise duration set-timeout <seconds> Set advertise timeout back Return to main menu version Display version quit Quit program exit Quit program help --- client/main.c | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/client/main.c b/client/main.c index cf04047cb..c5703c184 100644 --- a/client/main.c +++ b/client/main.c @@ -2358,6 +2358,30 @@ static void cmd_set_advertise_timeout(const char *arg) ad_advertise_timeout(dbus_conn, value); } +static const struct bt_shell_menu advertise_menu = { + .name = "advertise", + .entries = { + { "set-uuids", "[uuid1 uuid2 ...]", + cmd_set_advertise_uuids, "Set advertise uuids" }, + { "set-service", "[uuid][data=[xx xx ...]", cmd_set_advertise_service, + "Set advertise service data" }, + { "set-manufacturer", "[id][data=[xx xx ...]", + cmd_set_advertise_manufacturer, + "Set advertise manufacturer data" }, + { "set-tx-power", "<on/off>", cmd_set_advertise_tx_power, + "Enable/disable TX power to be advertised", + mode_generator }, + { "set-name", "<on/off/name>", cmd_set_advertise_name, + "Enable/disable local name to be advertised" }, + { "set-appearance", "<value>", cmd_set_advertise_appearance, + "Set custom appearance to be advertised" }, + { "set-duration", "<seconds>", cmd_set_advertise_duration, + "Set advertise duration" }, + { "set-timeout", "<seconds>", cmd_set_advertise_timeout, + "Set advertise timeout" }, + { } }, +}; + static const struct bt_shell_menu main_menu = { .name = "main", .entries = { @@ -2389,26 +2413,6 @@ static const struct bt_shell_menu main_menu = { { "advertise", "<on/off/type>", cmd_advertise, "Enable/disable advertising with given type", ad_generator}, - { "set-advertise-uuids", "[uuid1 uuid2 ...]", - cmd_set_advertise_uuids, "Set advertise uuids" }, - { "set-advertise-service", "[uuid][data=[xx xx ...]", - cmd_set_advertise_service, - "Set advertise service data" }, - { "set-advertise-manufacturer", "[id][data=[xx xx ...]", - cmd_set_advertise_manufacturer, - "Set advertise manufacturer data" }, - { "set-advertise-tx-power", "<on/off>", - cmd_set_advertise_tx_power, - "Enable/disable TX power to be advertised", - mode_generator }, - { "set-advertise-name", "<on/off/name>", cmd_set_advertise_name, - "Enable/disable local name to be advertised" }, - { "set-advertise-appearance", "<value>", cmd_set_advertise_appearance, - "Set custom appearance to be advertised" }, - { "set-advertise-duration", "<seconds>", cmd_set_advertise_duration, - "Set advertise duration" }, - { "set-advertise-timeout", "<seconds>", cmd_set_advertise_timeout, - "Set advertise timeout" }, { "set-scan-filter-uuids", "[uuid1 uuid2 ...]", cmd_set_scan_filter_uuids, "Set scan filter uuids" }, { "set-scan-filter-rssi", "[rssi]", cmd_set_scan_filter_rssi, @@ -2534,6 +2538,7 @@ int main(int argc, char *argv[]) bt_shell_init(&argc, &argv); bt_shell_set_menu(&main_menu); + bt_shell_add_submenu(&advertise_menu); bt_shell_set_prompt(PROMPT_OFF); dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); -- 2.13.6 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 5/6] client: Move scan related commands to a submenu 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz ` (2 preceding siblings ...) 2017-11-16 10:59 ` [PATCH v3 4/6] client: Move advertise related commands to a submenu Luiz Augusto von Dentz @ 2017-11-16 10:59 ` Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 6/6] client: Move gatt " Luiz Augusto von Dentz 2017-11-17 14:12 ` [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz 5 siblings, 0 replies; 7+ messages in thread From: Luiz Augusto von Dentz @ 2017-11-16 10:59 UTC (permalink / raw) To: linux-bluetooth From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> All commands related to scan settings are now under scan submenu: [bluetooth]# menu scan Menu scan: Available commands: ------------------- set-filter-uuids [uuid1 uuid2 ...] Set scan filter uuids set-filter-rssi [rssi] Set scan filter rssi, and clears pathloss set-filter-pathloss [pathloss] Set scan filter pathloss, and clears rssi set-filter-transport [transport] Set scan filter transport set-filter-duplicate-data [on/off] Set scan filter duplicate data set-filter-clear Clears discovery filter. back Return to main menu version Display version quit Quit program exit Quit program help Display help about this program --- client/main.c | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/client/main.c b/client/main.c index c5703c184..a60228833 100644 --- a/client/main.c +++ b/client/main.c @@ -2382,6 +2382,26 @@ static const struct bt_shell_menu advertise_menu = { { } }, }; +static const struct bt_shell_menu scan_menu = { + .name = "scan", + .entries = { + { "set-filter-uuids", "[uuid1 uuid2 ...]", cmd_set_scan_filter_uuids, + "Set scan filter uuids" }, + { "set-filter-rssi", "[rssi]", cmd_set_scan_filter_rssi, + "Set scan filter rssi, and clears pathloss" }, + { "set-filter-pathloss", "[pathloss]", cmd_set_scan_filter_pathloss, + "Set scan filter pathloss, and clears rssi" }, + { "set-filter-transport", "[transport]", cmd_set_scan_filter_transport, + "Set scan filter transport" }, + { "set-filter-duplicate-data", "[on/off]", + cmd_set_scan_filter_duplicate_data, + "Set scan filter duplicate data", + mode_generator }, + { "set-filter-clear", "", cmd_set_scan_filter_clear, + "Clears discovery filter." }, + { } }, +}; + static const struct bt_shell_menu main_menu = { .name = "main", .entries = { @@ -2413,21 +2433,6 @@ static const struct bt_shell_menu main_menu = { { "advertise", "<on/off/type>", cmd_advertise, "Enable/disable advertising with given type", ad_generator}, - { "set-scan-filter-uuids", "[uuid1 uuid2 ...]", - cmd_set_scan_filter_uuids, "Set scan filter uuids" }, - { "set-scan-filter-rssi", "[rssi]", cmd_set_scan_filter_rssi, - "Set scan filter rssi, and clears pathloss" }, - { "set-scan-filter-pathloss", "[pathloss]", - cmd_set_scan_filter_pathloss, - "Set scan filter pathloss, and clears rssi" }, - { "set-scan-filter-transport", "[transport]", - cmd_set_scan_filter_transport, "Set scan filter transport" }, - { "set-scan-filter-duplicate-data", "[on/off]", - cmd_set_scan_filter_duplicate_data, - "Set scan filter duplicate data", - mode_generator }, - { "set-scan-filter-clear", "", cmd_set_scan_filter_clear, - "Clears discovery filter." }, { "scan", "<on/off>", cmd_scan, "Scan for devices", mode_generator }, { "info", "[dev]", cmd_info, "Device information", @@ -2539,6 +2544,7 @@ int main(int argc, char *argv[]) bt_shell_init(&argc, &argv); bt_shell_set_menu(&main_menu); bt_shell_add_submenu(&advertise_menu); + bt_shell_add_submenu(&scan_menu); bt_shell_set_prompt(PROMPT_OFF); dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); -- 2.13.6 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v3 6/6] client: Move gatt related commands to a submenu 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz ` (3 preceding siblings ...) 2017-11-16 10:59 ` [PATCH v3 5/6] client: Move scan " Luiz Augusto von Dentz @ 2017-11-16 10:59 ` Luiz Augusto von Dentz 2017-11-17 14:12 ` [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz 5 siblings, 0 replies; 7+ messages in thread From: Luiz Augusto von Dentz @ 2017-11-16 10:59 UTC (permalink / raw) To: linux-bluetooth From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> All commands related to gatt settings are now under gatt submenu: [bluetooth]# menu gatt Menu gatt: Available commands: ------------------- list-attributes [dev] List attributes set-alias <alias> Set device alias select-attribute <attribute/UUID> Select attribute attribute-info [attribute/UUID] Select attribute read Read attribute value write <data=[xx xx ...]> Write attribute value acquire-write Acquire Write file descriptor release-write Release Write file descriptor acquire-notify Acquire Notify file descriptor release-notify Release Notify file descriptor notify <on/off> Notify attribute value register-application [UUID ...] Register profile to connect unregister-application Unregister profile register-service <UUID> Register application service. unregister-service <UUID/object> Unregister application service register-characteristic <UUID> <Flags=read,write,notify...> Register application characteristic unregister-characteristic <UUID/object> Unregister application characteristic register-descriptor <UUID> <Flags=read,write...> Register application descriptor unregister-descriptor <UUID/object> Unregister application descriptor back Return to main menu version Display version quit Quit program exit Quit program help Display help about this program --- client/main.c | 87 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/client/main.c b/client/main.c index a60228833..88d11bb29 100644 --- a/client/main.c +++ b/client/main.c @@ -2402,6 +2402,52 @@ static const struct bt_shell_menu scan_menu = { { } }, }; +static const struct bt_shell_menu gatt_menu = { + .name = "gatt", + .entries = { + { "list-attributes", "[dev]", cmd_list_attributes, "List attributes", + dev_generator }, + { "set-alias", "<alias>", cmd_set_alias, "Set device alias" }, + { "select-attribute", "<attribute/UUID>", cmd_select_attribute, + "Select attribute", attribute_generator }, + { "attribute-info", "[attribute/UUID]", cmd_attribute_info, + "Select attribute", attribute_generator }, + { "read", NULL, cmd_read, "Read attribute value" }, + { "write", "<data=[xx xx ...]>", cmd_write, + "Write attribute value" }, + { "acquire-write", NULL, cmd_acquire_write, + "Acquire Write file descriptor" }, + { "release-write", NULL, cmd_release_write, + "Release Write file descriptor" }, + { "acquire-notify", NULL, cmd_acquire_notify, + "Acquire Notify file descriptor" }, + { "release-notify", NULL, cmd_release_notify, + "Release Notify file descriptor" }, + { "notify", "<on/off>", cmd_notify, "Notify attribute value", + mode_generator }, + { "register-application", "[UUID ...]", cmd_register_app, + "Register profile to connect" }, + { "unregister-application", NULL, cmd_unregister_app, + "Unregister profile" }, + { "register-service", "<UUID>", cmd_register_service, + "Register application service." }, + { "unregister-service", "<UUID/object>", cmd_unregister_service, + "Unregister application service" }, + { "register-characteristic", "<UUID> <Flags=read,write,notify...>", + cmd_register_characteristic, + "Register application characteristic" }, + { "unregister-characteristic", "<UUID/object>", + cmd_unregister_characteristic, + "Unregister application characteristic" }, + { "register-descriptor", "<UUID> <Flags=read,write...>", + cmd_register_descriptor, + "Register application descriptor" }, + { "unregister-descriptor", "<UUID/object>", + cmd_unregister_descriptor, + "Unregister application descriptor" }, + { } }, +}; + static const struct bt_shell_menu main_menu = { .name = "main", .entries = { @@ -2453,46 +2499,6 @@ static const struct bt_shell_menu main_menu = { dev_generator }, { "disconnect", "[dev]", cmd_disconn, "Disconnect device", dev_generator }, - { "list-attributes", "[dev]", cmd_list_attributes, "List attributes", - dev_generator }, - { "set-alias", "<alias>", cmd_set_alias, "Set device alias" }, - { "select-attribute", "<attribute/UUID>", cmd_select_attribute, - "Select attribute", attribute_generator }, - { "attribute-info", "[attribute/UUID]", cmd_attribute_info, - "Select attribute", attribute_generator }, - { "read", NULL, cmd_read, "Read attribute value" }, - { "write", "<data=[xx xx ...]>", cmd_write, - "Write attribute value" }, - { "acquire-write", NULL, cmd_acquire_write, - "Acquire Write file descriptor" }, - { "release-write", NULL, cmd_release_write, - "Release Write file descriptor" }, - { "acquire-notify", NULL, cmd_acquire_notify, - "Acquire Notify file descriptor" }, - { "release-notify", NULL, cmd_release_notify, - "Release Notify file descriptor" }, - { "notify", "<on/off>", cmd_notify, "Notify attribute value", - mode_generator }, - { "register-application", "[UUID ...]", cmd_register_app, - "Register profile to connect" }, - { "unregister-application", NULL, cmd_unregister_app, - "Unregister profile" }, - { "register-service", "<UUID>", cmd_register_service, - "Register application service." }, - { "unregister-service", "<UUID/object>", cmd_unregister_service, - "Unregister application service" }, - { "register-characteristic", "<UUID> <Flags=read,write,notify...>", - cmd_register_characteristic, - "Register application characteristic" }, - { "unregister-characteristic", "<UUID/object>", - cmd_unregister_characteristic, - "Unregister application characteristic" }, - { "register-descriptor", "<UUID> <Flags=read,write...>", - cmd_register_descriptor, - "Register application descriptor" }, - { "unregister-descriptor", "<UUID/object>", - cmd_unregister_descriptor, - "Unregister application descriptor" }, { } }, }; @@ -2545,6 +2551,7 @@ int main(int argc, char *argv[]) bt_shell_set_menu(&main_menu); bt_shell_add_submenu(&advertise_menu); bt_shell_add_submenu(&scan_menu); + bt_shell_add_submenu(&gatt_menu); bt_shell_set_prompt(PROMPT_OFF); dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); -- 2.13.6 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v3 1/6] shared/shell: Add initial implementation 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz ` (4 preceding siblings ...) 2017-11-16 10:59 ` [PATCH v3 6/6] client: Move gatt " Luiz Augusto von Dentz @ 2017-11-17 14:12 ` Luiz Augusto von Dentz 5 siblings, 0 replies; 7+ messages in thread From: Luiz Augusto von Dentz @ 2017-11-17 14:12 UTC (permalink / raw) To: linux-bluetooth@vger.kernel.org Hi, On Thu, Nov 16, 2017 at 12:59 PM, Luiz Augusto von Dentz <luiz.dentz@gmail.com> wrote: > From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> > > This add initial bt_shell helper which can be used to create shell-like > command line tools. > --- > v3: Add submenu changes > > Makefile.tools | 3 +- > src/shared/shell.c | 570 +++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/shared/shell.h | 67 +++++++ > 3 files changed, 639 insertions(+), 1 deletion(-) > create mode 100644 src/shared/shell.c > create mode 100644 src/shared/shell.h > > diff --git a/Makefile.tools b/Makefile.tools > index 561302fa1..dc2902cb7 100644 > --- a/Makefile.tools > +++ b/Makefile.tools > @@ -8,7 +8,8 @@ client_bluetoothctl_SOURCES = client/main.c \ > client/advertising.h \ > client/advertising.c \ > client/gatt.h client/gatt.c \ > - monitor/uuid.h monitor/uuid.c > + monitor/uuid.h monitor/uuid.c \ > + src/shared/shell.h src/shared/shell.c > client_bluetoothctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ > @GLIB_LIBS@ @DBUS_LIBS@ -lreadline > endif > diff --git a/src/shared/shell.c b/src/shared/shell.c > new file mode 100644 > index 000000000..7db629bf1 > --- /dev/null > +++ b/src/shared/shell.c > @@ -0,0 +1,570 @@ > +/* > + * > + * BlueZ - Bluetooth protocol stack for Linux > + * > + * Copyright (C) 2017 Intel Corporation. All rights reserved. > + * > + * > + * This library is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * This library is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with this library; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > + * > + */ > + > +#ifdef HAVE_CONFIG_H > +#include <config.h> > +#endif > + > +#include <stdio.h> > +#include <errno.h> > +#include <unistd.h> > +#include <stdlib.h> > +#include <stdbool.h> > +#include <signal.h> > +#include <sys/signalfd.h> > + > +#include <readline/readline.h> > +#include <readline/history.h> > +#include <glib.h> > + > +#include "src/shared/io.h" > +#include "src/shared/util.h" > +#include "src/shared/queue.h" > +#include "src/shared/shell.h" > + > +#define CMD_LENGTH 48 > +#define print_text(color, fmt, args...) \ > + printf(color fmt COLOR_OFF "\n", ## args) > +#define print_menu(cmd, args, desc) \ > + printf(COLOR_HIGHLIGHT "%s %-*s " COLOR_OFF "%s\n", \ > + cmd, (int)(CMD_LENGTH - strlen(cmd)), args, desc) > + > +static GMainLoop *main_loop; > +static gboolean option_version = FALSE; > + > +static struct { > + struct io *input; > + > + bool saved_prompt; > + bt_shell_prompt_input_func saved_func; > + void *saved_user_data; > + > + const struct bt_shell_menu_entry *menu; > + /* TODO: Add submenus support */ > +} data; > + > +static void shell_print_menu(void); > + > +static void cmd_version(const char *arg) > +{ > + bt_shell_printf("Version %s\n", VERSION); > +} > + > +static void cmd_quit(const char *arg) > +{ > + g_main_loop_quit(main_loop); > +} > + > +static void cmd_help(const char *arg) > +{ > + shell_print_menu(); > +} > + > +static const struct bt_shell_menu_entry default_menu[] = { > + { "version", NULL, cmd_version, "Display version" }, > + { "quit", NULL, cmd_quit, "Quit program" }, > + { "exit", NULL, cmd_quit, "Quit program" }, > + { "help", NULL, cmd_help, > + "Display help about this program" }, > + { } > +}; > + > +static void shell_print_menu(void) > +{ > + const struct bt_shell_menu_entry *entry; > + > + if (!data.menu) > + return; > + > + print_text(COLOR_HIGHLIGHT, "Available commands:"); > + print_text(COLOR_HIGHLIGHT, "-------------------"); > + for (entry = data.menu; entry->cmd; entry++) { > + print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); > + } > + > + for (entry = default_menu; entry->cmd; entry++) { > + print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); > + } > +} > + > +static void shell_exec(const char *cmd, const char *arg) > +{ > + const struct bt_shell_menu_entry *entry; > + > + if (!data.menu || !cmd) > + return; > + > + for (entry = data.menu; entry->cmd; entry++) { > + if (strcmp(cmd, entry->cmd)) > + continue; > + > + if (entry->func) { > + entry->func(arg); > + return; > + } > + } > + > + for (entry = default_menu; entry->cmd; entry++) { > + if (strcmp(cmd, entry->cmd)) > + continue; > + > + if (entry->func) { > + entry->func(arg); > + return; > + } > + } > + > + print_text(COLOR_HIGHLIGHT, "Invalid command"); > +} > + > +void bt_shell_printf(const char *fmt, ...) > +{ > + va_list args; > + bool save_input; > + char *saved_line; > + int saved_point; > + > + save_input = !RL_ISSTATE(RL_STATE_DONE); > + > + if (save_input) { > + saved_point = rl_point; > + saved_line = rl_copy_text(0, rl_end); > + if (!data.saved_prompt) { > + rl_save_prompt(); > + rl_replace_line("", 0); > + rl_redisplay(); > + } > + } > + > + va_start(args, fmt); > + vprintf(fmt, args); > + va_end(args); > + > + if (save_input) { > + if (!data.saved_prompt) > + rl_restore_prompt(); > + rl_replace_line(saved_line, 0); > + rl_point = saved_point; > + rl_forced_update_display(); > + free(saved_line); > + } > +} > + > +void bt_shell_hexdump(const unsigned char *buf, size_t len) > +{ > + static const char hexdigits[] = "0123456789abcdef"; > + char str[68]; > + size_t i; > + > + if (!len) > + return; > + > + str[0] = ' '; > + > + for (i = 0; i < len; i++) { > + str[((i % 16) * 3) + 1] = ' '; > + str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4]; > + str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf]; > + str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.'; > + > + if ((i + 1) % 16 == 0) { > + str[49] = ' '; > + str[50] = ' '; > + str[67] = '\0'; > + bt_shell_printf("%s\n", str); > + str[0] = ' '; > + } > + } > + > + if (i % 16 > 0) { > + size_t j; > + for (j = (i % 16); j < 16; j++) { > + str[(j * 3) + 1] = ' '; > + str[(j * 3) + 2] = ' '; > + str[(j * 3) + 3] = ' '; > + str[j + 51] = ' '; > + } > + str[49] = ' '; > + str[50] = ' '; > + str[67] = '\0'; > + bt_shell_printf("%s\n", str); > + } > +} > + > +void bt_shell_prompt_input(const char *label, const char *msg, > + bt_shell_prompt_input_func func, void *user_data) > +{ > + /* Normal use should not prompt for user input to the value a second > + * time before it releases the prompt, but we take a safe action. */ > + if (data.saved_prompt) > + return; > + > + rl_save_prompt(); > + rl_message(COLOR_RED "[%s]" COLOR_OFF " %s ", label, msg); > + > + data.saved_prompt = true; > + data.saved_func = func; > + data.saved_user_data = user_data; > +} > + > +int bt_shell_release_prompt(const char *input) > +{ > + bt_shell_prompt_input_func func; > + void *user_data; > + > + if (!data.saved_prompt) > + return -1; > + > + data.saved_prompt = false; > + > + rl_restore_prompt(); > + > + func = data.saved_func; > + user_data = data.saved_user_data; > + > + data.saved_func = NULL; > + data.saved_user_data = NULL; > + > + func(input, user_data); > + > + return 0; > +} > + > +static void rl_handler(char *input) > +{ > + char *cmd, *arg; > + > + if (!input) { > + rl_insert_text("quit"); > + rl_redisplay(); > + rl_crlf(); > + g_main_loop_quit(main_loop); > + return; > + } > + > + if (!strlen(input)) > + goto done; > + > + if (!bt_shell_release_prompt(input)) > + goto done; > + > + if (history_search(input, -1)) > + add_history(input); > + > + cmd = strtok_r(input, " ", &arg); > + if (!cmd) > + goto done; > + > + if (arg) { > + int len = strlen(arg); > + if (len > 0 && arg[len - 1] == ' ') > + arg[len - 1] = '\0'; > + } > + > + shell_exec(cmd, arg); > +done: > + free(input); > +} > + > +static char *cmd_generator(const char *text, int state) > +{ > + static const struct bt_shell_menu_entry *entry; > + static int index, len; > + const char *cmd; > + > + if (!state) { > + entry = default_menu; > + index = 0; > + len = strlen(text); > + } > + > + while ((cmd = entry[index].cmd)) { > + index++; > + > + if (!strncmp(cmd, text, len)) > + return strdup(cmd); > + } > + > + if (state) > + return NULL; > + > + entry = data.menu; > + index = 0; > + > + return cmd_generator(text, 1); > +} > + > +static char **menu_completion(const struct bt_shell_menu_entry *entry, > + const char *text, char *input_cmd) > +{ > + char **matches = NULL; > + > + for (entry = data.menu; entry->cmd; entry++) { > + if (strcmp(entry->cmd, input_cmd)) > + continue; > + > + if (!entry->gen) > + continue; > + > + rl_completion_display_matches_hook = entry->disp; > + matches = rl_completion_matches(text, entry->gen); > + break; > + } > + > + return matches; > +} > + > +static char **shell_completion(const char *text, int start, int end) > +{ > + char **matches = NULL; > + > + if (!data.menu) > + return NULL; > + > + if (start > 0) { > + char *input_cmd; > + > + input_cmd = strndup(rl_line_buffer, start - 1); > + matches = menu_completion(default_menu, text, input_cmd); > + if (!matches) > + matches = menu_completion(data.menu, text, > + input_cmd); > + > + free(input_cmd); > + } else { > + rl_completion_display_matches_hook = NULL; > + matches = rl_completion_matches(text, cmd_generator); > + } > + > + if (!matches) > + rl_attempted_completion_over = 1; > + > + return matches; > +} > + > +static bool io_hup(struct io *io, void *user_data) > +{ > + g_main_loop_quit(main_loop); > + > + return false; > +} > + > +static bool signal_read(struct io *io, void *user_data) > +{ > + static bool terminated = false; > + struct signalfd_siginfo si; > + ssize_t result; > + int fd; > + > + fd = io_get_fd(io); > + > + result = read(fd, &si, sizeof(si)); > + if (result != sizeof(si)) > + return false; > + > + switch (si.ssi_signo) { > + case SIGINT: > + if (data.input) { > + rl_replace_line("", 0); > + rl_crlf(); > + rl_on_new_line(); > + rl_redisplay(); > + break; > + } > + > + /* > + * If input was not yet setup up that means signal was received > + * while daemon was not yet running. Since user is not able > + * to terminate client by CTRL-D or typing exit treat this as > + * exit and fall through. > + */ > + > + /* fall through */ > + case SIGTERM: > + if (!terminated) { > + rl_replace_line("", 0); > + rl_crlf(); > + g_main_loop_quit(main_loop); > + } > + > + terminated = true; > + break; > + } > + > + return false; > +} > + > +static struct io *setup_signalfd(void) > +{ > + struct io *io; > + sigset_t mask; > + int fd; > + > + sigemptyset(&mask); > + sigaddset(&mask, SIGINT); > + sigaddset(&mask, SIGTERM); > + > + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { > + perror("Failed to set signal mask"); > + return 0; > + } > + > + fd = signalfd(-1, &mask, 0); > + if (fd < 0) { > + perror("Failed to create signal descriptor"); > + return 0; > + } > + > + io = io_new(fd); > + > + io_set_close_on_destroy(io, true); > + io_set_read_handler(io, signal_read, NULL, NULL); > + io_set_disconnect_handler(io, io_hup, NULL, NULL); > + > + return io; > +} > + > +static GOptionEntry options[] = { > + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, > + "Show version information and exit" }, > + { NULL }, > +}; > + > +static void rl_init(void) > +{ > + setlinebuf(stdout); > + rl_attempted_completion_function = shell_completion; > + > + rl_erase_empty_line = 1; > + rl_callback_handler_install(NULL, rl_handler); > +} > + > +void bt_shell_init(int *argc, char ***argv) > +{ > + GOptionContext *context; > + GError *error = NULL; > + > + context = g_option_context_new(NULL); > + g_option_context_add_main_entries(context, options, NULL); > + > + if (g_option_context_parse(context, argc, argv, &error) == FALSE) { > + if (error != NULL) { > + g_printerr("%s\n", error->message); > + g_error_free(error); > + } else > + g_printerr("An unknown error occurred\n"); > + exit(1); > + } > + > + g_option_context_free(context); > + > + if (option_version == TRUE) { > + g_print("%s\n", VERSION); > + exit(EXIT_SUCCESS); > + } > + > + main_loop = g_main_loop_new(NULL, FALSE); > + > + rl_init(); > +} > + > +static void rl_cleanup(void) > +{ > + rl_message(""); > + rl_callback_handler_remove(); > +} > + > +void bt_shell_run(void) > +{ > + struct io *signal; > + > + signal = setup_signalfd(); > + > + g_main_loop_run(main_loop); > + > + bt_shell_release_prompt(""); > + bt_shell_detach(); > + > + io_destroy(signal); > + > + g_main_loop_unref(main_loop); > + main_loop = NULL; > + > + rl_cleanup(); > +} > + > +bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu) > +{ > + if (data.menu || !menu) > + return false; > + > + data.menu = menu; > + > + return true; > +} > + > +void bt_shell_set_prompt(const char *string) > +{ > + if (!main_loop) > + return; > + > + rl_set_prompt(string); > + printf("\r"); > + rl_on_new_line(); > + rl_redisplay(); > +} > + > +static bool input_read(struct io *io, void *user_data) > +{ > + rl_callback_read_char(); > + return true; > +} > + > +bool bt_shell_attach(int fd) > +{ > + struct io *io; > + > + /* TODO: Allow more than one input? */ > + if (data.input) > + return false; > + > + io = io_new(fd); > + > + io_set_read_handler(io, input_read, NULL, NULL); > + io_set_disconnect_handler(io, io_hup, NULL, NULL); > + > + data.input = io; > + > + return true; > +} > + > +bool bt_shell_detach(void) > +{ > + if (!data.input) > + return false; > + > + io_destroy(data.input); > + data.input = NULL; > + > + return true; > +} > diff --git a/src/shared/shell.h b/src/shared/shell.h > new file mode 100644 > index 000000000..843335784 > --- /dev/null > +++ b/src/shared/shell.h > @@ -0,0 +1,67 @@ > +/* > + * > + * BlueZ - Bluetooth protocol stack for Linux > + * > + * Copyright (C) 2017 Intel Corporation. All rights reserved. > + * > + * > + * This library is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * This library is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with this library; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA > + * > + */ > + > +#define COLOR_OFF "\x1B[0m" > +#define COLOR_RED "\x1B[0;91m" > +#define COLOR_GREEN "\x1B[0;92m" > +#define COLOR_YELLOW "\x1B[0;93m" > +#define COLOR_BLUE "\x1B[0;94m" > +#define COLOR_BOLDGRAY "\x1B[1;30m" > +#define COLOR_BOLDWHITE "\x1B[1;37m" > +#define COLOR_HIGHLIGHT "\x1B[1;39m" > + > +typedef void (*bt_shell_menu_cb_t)(const char *arg); > +typedef char * (*bt_shell_menu_gen_t)(const char *text, int state); > +typedef void (*bt_shell_menu_disp_t) (char **matches, int num_matches, > + int max_length); > +typedef void (*bt_shell_prompt_input_func) (const char *input, void *user_data); > + > +struct bt_shell_menu_entry { > + const char *cmd; > + const char *arg; > + bt_shell_menu_cb_t func; > + const char *desc; > + bt_shell_menu_gen_t gen; > + bt_shell_menu_disp_t disp; > +}; > + > +void bt_shell_init(int *argc, char ***argv); > + > +void bt_shell_run(void); > + > +bool bt_shell_set_menu(const struct bt_shell_menu_entry *menu); > + > +void bt_shell_set_prompt(const char *string); > + > +void bt_shell_printf(const char *fmt, > + ...) __attribute__((format(printf, 1, 2))); > +void bt_shell_hexdump(const unsigned char *buf, size_t len); > + > +void bt_shell_prompt_input(const char *label, const char *msg, > + bt_shell_prompt_input_func func, void *user_data); > +int bt_shell_release_prompt(const char *input); > + > +bool bt_shell_attach(int fd); > +bool bt_shell_detach(void); > + > +void bt_shell_cleanup(void); > -- > 2.13.6 Applied. -- Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2017-11-17 14:12 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2017-11-16 10:59 [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 2/6] client: Make use of bt_shell Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 3/6] shared/shell: Add submenu support Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 4/6] client: Move advertise related commands to a submenu Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 5/6] client: Move scan " Luiz Augusto von Dentz 2017-11-16 10:59 ` [PATCH v3 6/6] client: Move gatt " Luiz Augusto von Dentz 2017-11-17 14:12 ` [PATCH v3 1/6] shared/shell: Add initial implementation Luiz Augusto von Dentz
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).