linux-bluetooth.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [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, &notify_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).