2013-10-18 13:15:57

by Jerzy Kasenberg

[permalink] [raw]
Subject: [PATCH 0/3] Tab completion for haltest tool

This patchset add tab completion to haltest so using this tool
will be much easier.

It allows to build for andriod 4.3 and for host with autotools.

Jerzy Kasenberg (3):
android: Add tab completion to haltest
android: Fix error in draw line in haltest tool
android: Add completion for adapter to haltest

Makefile.android | 2 +
android/Android.mk | 1 +
android/client/haltest.c | 11 +-
android/client/if-bt.c | 331 ++++++++++++++++++++++++++++++++++------
android/client/if-main.h | 44 +++++-
android/client/tabcompletion.c | 313 +++++++++++++++++++++++++++++++++++++
android/client/terminal.c | 4 +-
android/client/textconv.c | 21 +++
android/client/textconv.h | 4 +
9 files changed, 673 insertions(+), 58 deletions(-)
create mode 100644 android/client/tabcompletion.c

--
1.7.9.5



2013-10-18 13:51:13

by Johan Hedberg

[permalink] [raw]
Subject: Re: [PATCH 0/3] Tab completion for haltest tool

Hi Jerzy,

On Fri, Oct 18, 2013, Jerzy Kasenberg wrote:
> This patchset add tab completion to haltest so using this tool
> will be much easier.
>
> It allows to build for andriod 4.3 and for host with autotools.
>
> Jerzy Kasenberg (3):
> android: Add tab completion to haltest
> android: Fix error in draw line in haltest tool
> android: Add completion for adapter to haltest
>
> Makefile.android | 2 +
> android/Android.mk | 1 +
> android/client/haltest.c | 11 +-
> android/client/if-bt.c | 331 ++++++++++++++++++++++++++++++++++------
> android/client/if-main.h | 44 +++++-
> android/client/tabcompletion.c | 313 +++++++++++++++++++++++++++++++++++++
> android/client/terminal.c | 4 +-
> android/client/textconv.c | 21 +++
> android/client/textconv.h | 4 +
> 9 files changed, 673 insertions(+), 58 deletions(-)
> create mode 100644 android/client/tabcompletion.c

All three patches have been applied. Thanks.

Johan

2013-10-18 13:16:00

by Jerzy Kasenberg

[permalink] [raw]
Subject: [PATCH 3/3] android: Add completion for adapter to haltest

This patch adds all completion functions for adapter methods.
It also adds short help lines for all methods that require arguments.
---
android/client/haltest.c | 11 +-
android/client/if-bt.c | 331 ++++++++++++++++++++++++++++++++++++++-------
android/client/if-main.h | 16 +++
android/client/textconv.c | 21 +++
android/client/textconv.h | 4 +
5 files changed, 330 insertions(+), 53 deletions(-)

diff --git a/android/client/haltest.c b/android/client/haltest.c
index 6dcabbc..6b37f33 100644
--- a/android/client/haltest.c
+++ b/android/client/haltest.c
@@ -103,11 +103,12 @@ static void process_line(char *line_buffer)
continue;
}
if (argc < 2 || strcmp(argv[1], "?") == 0) {
- j = 0;
- while (strcmp(interfaces[i]->methods[j].name, "")) {
- haltest_info("%s %s\n", argv[0],
- interfaces[i]->methods[j].name);
- ++j;
+ struct method *m = &interfaces[i]->methods[0];
+ while (strcmp(m->name, "")) {
+ haltest_info("%s %s %s\n", argv[0],
+ m->name,
+ (m->help ? m->help : ""));
+ m++;
}
return;
}
diff --git a/android/client/if-bt.c b/android/client/if-bt.c
index 90ef732..30b41cd 100644
--- a/android/client/if-bt.c
+++ b/android/client/if-bt.c
@@ -19,6 +19,16 @@

const bt_interface_t *if_bluetooth;

+#define VERIFY_PROP_TYPE_ARG(n, typ) \
+ do { \
+ if (n < argc) \
+ typ = str2btpropertytype(argv[n]); \
+ else { \
+ haltest_error("No property type specified\n"); \
+ return;\
+ } \
+ } while (0)
+
static char *bdaddr2str(const bt_bdaddr_t *bd_addr)
{
static char buf[18];
@@ -177,6 +187,84 @@ static void dump_properties(int num_properties, bt_property_t *properties)
}
}

+/*
+ * Cache for remote devices, stored in sorted array
+ */
+static bt_bdaddr_t *remote_devices = NULL;
+static int remote_devices_cnt = 0;
+static int remote_devices_capacity = 0;
+
+/* Adds address to remote device set so it can be used in tab completion */
+void add_remote_device(const bt_bdaddr_t *addr)
+{
+ int i;
+
+ if (remote_devices == NULL) {
+ remote_devices = malloc(4 * sizeof(bt_bdaddr_t));
+ remote_devices_cnt = 0;
+ if (remote_devices == NULL) {
+ remote_devices_capacity = 0;
+ return;
+ }
+ remote_devices_capacity = 4;
+ }
+
+ /* Array is sorted, search for right place */
+ for (i = 0; i < remote_devices_cnt; ++i) {
+ int res = memcmp(&remote_devices[i], addr, sizeof(*addr));
+ if (res == 0)
+ return; /* Already added */
+ else if (res > 0)
+ break;
+ }
+
+ /* Realloc space if needed */
+ if (remote_devices_cnt >= remote_devices_capacity) {
+ remote_devices_capacity *= 2;
+ remote_devices = realloc(remote_devices, sizeof(bt_bdaddr_t) *
+ remote_devices_capacity);
+ if (remote_devices == NULL) {
+ remote_devices_capacity = 0;
+ remote_devices_cnt = 0;
+ return;
+ }
+ }
+
+ if (i < remote_devices_cnt)
+ memmove(remote_devices + i + 1, remote_devices + i,
+ (remote_devices_cnt - i) * sizeof(bt_bdaddr_t));
+ remote_devices[i] = *addr;
+ remote_devices_cnt++;
+}
+
+const char *enum_devices(void *v, int i)
+{
+ static char buf[19];
+
+ if (i >= remote_devices_cnt)
+ return NULL;
+
+ bt_bdaddr_t2str(&remote_devices[i], buf);
+ return buf;
+}
+
+static void add_remote_device_from_props(int num_properties,
+ const bt_property_t *properties)
+{
+ int i;
+
+ for (i = 0; i < num_properties; i++) {
+ /*
+ * properities sometimes come unaligned hence memcp to
+ * aligned buffer
+ */
+ bt_property_t property;
+ memcpy(&property, properties + i, sizeof(property));
+ if (property.type == BT_PROPERTY_BDADDR)
+ add_remote_device((bt_bdaddr_t *) property.val);
+ }
+}
+
static void adapter_state_changed_cb(bt_state_t state)
{
haltest_info("%s: state=%s\n", __func__, bt_state_t2str(state));
@@ -198,6 +286,8 @@ static void remote_device_properties_cb(bt_status_t status,
__func__, bt_status_t2str(status), bdaddr2str(bd_addr),
num_properties);

+ add_remote_device(bd_addr);
+
dump_properties(num_properties, properties);
}

@@ -205,6 +295,8 @@ static void device_found_cb(int num_properties, bt_property_t *properties)
{
haltest_info("%s: num_properties=%d\n", __func__, num_properties);

+ add_remote_device_from_props(num_properties, properties);
+
dump_properties(num_properties, properties);
}

@@ -214,19 +306,33 @@ static void discovery_state_changed_cb(bt_discovery_state_t state)
bt_discovery_state_t2str(state));
}

+/*
+ * Buffer for remote addres that came from one of bind request.
+ * It's stored for command completion.
+ */
+static char last_remote_addr[18];
+static bt_ssp_variant_t last_ssp_variant = (bt_ssp_variant_t)-1;
+
static void pin_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name,
uint32_t cod)
{
+ /* Store for command completion */
+ bt_bdaddr_t2str(remote_bd_addr, last_remote_addr);
+
haltest_info("%s: remote_bd_addr=%s bd_name=%s cod=%06x\n", __func__,
- bdaddr2str(remote_bd_addr), bd_name->name, cod);
+ last_remote_addr, bd_name->name, cod);
}

static void ssp_request_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *bd_name,
uint32_t cod, bt_ssp_variant_t pairing_variant,
uint32_t pass_key)
{
+ /* Store for command completion */
+ bt_bdaddr_t2str(remote_bd_addr, last_remote_addr);
+ last_ssp_variant = pairing_variant;
+
haltest_info("%s: remote_bd_addr=%s bd_name=%s cod=%06x pairing_variant=%s pass_key=%d\n",
- __func__, bdaddr2str(remote_bd_addr), bd_name->name, cod,
+ __func__, last_remote_addr, bd_name->name, cod,
bt_ssp_variant_t2str(pairing_variant), pass_key);
}

@@ -338,15 +444,45 @@ static void get_adapter_properties_p(int argc, const char **argv)
EXEC(if_bluetooth->get_adapter_properties);
}

+static void get_adapter_property_c(int argc, const char **argv,
+ enum_func *penum_func, void **puser)
+{
+ if (argc == 3) {
+ *puser = TYPE_ENUM(bt_property_type_t);
+ *penum_func = enum_defines;
+ }
+}
+
static void get_adapter_property_p(int argc, const char **argv)
{
- int type = str2btpropertytype(argv[2]);
+ int type;

RETURN_IF_NULL(if_bluetooth);
+ VERIFY_PROP_TYPE_ARG(2, type);

EXEC(if_bluetooth->get_adapter_property, type);
}

+static const char * const names[] = {
+ "BT_PROPERTY_BDNAME",
+ "BT_PROPERTY_ADAPTER_SCAN_MODE",
+ "BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT", NULL
+};
+
+static void set_adapter_property_c(int argc, const char **argv,
+ enum_func *penum_func, void **puser)
+{
+ if (argc == 3) {
+ *puser = (void *) names;
+ *penum_func = enum_strings;
+ } else if (argc == 4) {
+ if (0 == strcmp(argv[2], "BT_PROPERTY_ADAPTER_SCAN_MODE")) {
+ *puser = TYPE_ENUM(bt_scan_mode_t);
+ *penum_func = enum_defines;
+ }
+ }
+}
+
static void set_adapter_property_p(int argc, const char **argv)
{
bt_property_t property;
@@ -354,9 +490,12 @@ static void set_adapter_property_p(int argc, const char **argv)
int timeout;

RETURN_IF_NULL(if_bluetooth);
+ VERIFY_PROP_TYPE_ARG(2, property.type);

- property.type = str2btpropertytype(argv[2]);
-
+ if (argc <= 3) {
+ haltest_error("No property value specified\n");
+ return;
+ }
switch (property.type) {
case BT_PROPERTY_BDNAME:
property.len = strlen(argv[3]) + 1;
@@ -383,39 +522,70 @@ static void set_adapter_property_p(int argc, const char **argv)
EXEC(if_bluetooth->set_adapter_property, &property);
}

+/*
+ * This function is to be used for completion methods that need only address
+ */
+static void complete_addr_c(int argc, const char **argv,
+ enum_func *penum_func, void **puser)
+{
+ if (argc == 3) {
+ *puser = NULL;
+ *penum_func = enum_devices;
+ }
+}
+
+/* Just addres to complete, use complete_addr_c */
+#define get_remote_device_properties_c complete_addr_c
+
static void get_remote_device_properties_p(int argc, const char **argv)
{
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
+ VERIFY_ADDR_ARG(2, &addr);

EXEC(if_bluetooth->get_remote_device_properties, &addr);
}

+static void get_remote_device_property_c(int argc, const char **argv,
+ enum_func *penum_func,
+ void **puser)
+{
+ if (argc == 3) {
+ *puser = NULL;
+ *penum_func = enum_devices;
+ } else if (argc == 4) {
+ *puser = TYPE_ENUM(bt_property_type_t);
+ *penum_func = enum_defines;
+ }
+}
+
static void get_remote_device_property_p(int argc, const char **argv)
{
bt_property_type_t type;
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
- type = str2btpropertytype(argv[3]);
+ VERIFY_ADDR_ARG(2, &addr);
+ VERIFY_PROP_TYPE_ARG(3, type);

EXEC(if_bluetooth->get_remote_device_property, &addr, type);
}

+/*
+ * Same completion as for get_remote_device_property_c can be used for
+ * set_remote_device_property_c. No need to create separate function.
+ */
+#define set_remote_device_property_c get_remote_device_property_c
+
static void set_remote_device_property_p(int argc, const char **argv)
{
bt_property_t property;
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
- property.type = str2btpropertytype(argv[3]);
+ VERIFY_ADDR_ARG(2, &addr);
+ VERIFY_PROP_TYPE_ARG(3, property.type);

switch (property.type) {
case BT_PROPERTY_REMOTE_FRIENDLY_NAME:
@@ -430,26 +600,37 @@ static void set_remote_device_property_p(int argc, const char **argv)
EXEC(if_bluetooth->set_remote_device_property, &addr, &property);
}

+/*
+ * For now uuid is not autocompleted. Use routine for complete_addr_c
+ */
+#define get_remote_service_record_c complete_addr_c
+
static void get_remote_service_record_p(int argc, const char **argv)
{
bt_bdaddr_t addr;
bt_uuid_t uuid;

RETURN_IF_NULL(if_bluetooth);
+ VERIFY_ADDR_ARG(2, &addr);

- str2bt_bdaddr_t(argv[2], &addr);
+ if (argc <= 3) {
+ haltest_error("No uuid specified\n");
+ return;
+ }
str2bt_uuid_t(argv[3], &uuid);

EXEC(if_bluetooth->get_remote_service_record, &addr, &uuid);
}

+/* Just addres to complete, use complete_addr_c */
+#define get_remote_services_c complete_addr_c
+
static void get_remote_services_p(int argc, const char **argv)
{
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
+ VERIFY_ADDR_ARG(2, &addr);

EXEC(if_bluetooth->get_remote_services, &addr);
}
@@ -468,39 +649,55 @@ static void cancel_discovery_p(int argc, const char **argv)
EXEC(if_bluetooth->cancel_discovery);
}

+/* Just addres to complete, use complete_addr_c */
+#define create_bond_c complete_addr_c
+
static void create_bond_p(int argc, const char **argv)
{
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
+ VERIFY_ADDR_ARG(2, &addr);

EXEC(if_bluetooth->create_bond, &addr);
}

+/* Just addres to complete, use complete_addr_c */
+#define remove_bond_c complete_addr_c
+
static void remove_bond_p(int argc, const char **argv)
{
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
+ VERIFY_ADDR_ARG(2, &addr);

EXEC(if_bluetooth->remove_bond, &addr);
}

+/* Just addres to complete, use complete_addr_c */
+#define cancel_bond_c complete_addr_c
+
static void cancel_bond_p(int argc, const char **argv)
{
bt_bdaddr_t addr;

RETURN_IF_NULL(if_bluetooth);
-
- str2bt_bdaddr_t(argv[2], &addr);
+ VERIFY_ADDR_ARG(2, &addr);

EXEC(if_bluetooth->cancel_bond, &addr);
}

+static void pin_reply_c(int argc, const char **argv,
+ enum_func *penum_func, void **puser)
+{
+ static const char *const completions[] = { last_remote_addr, NULL };
+ if (argc == 3) {
+ *puser = (void *) completions;
+ *penum_func = enum_strings;
+ }
+}
+
static void pin_reply_p(int argc, const char **argv)
{
bt_bdaddr_t addr;
@@ -509,14 +706,9 @@ static void pin_reply_p(int argc, const char **argv)
int accept;

RETURN_IF_NULL(if_bluetooth);
+ VERIFY_ADDR_ARG(2, &addr);

- if (argc < 3) {
- haltest_error("No address specified\n");
- return;
- }
- str2bt_bdaddr_t(argv[2], &addr);
-
- if (argc >= 4) {
+ if (argc > 3) {
accept = 1;
pin_len = strlen(argv[3]);
memcpy(pin.pin, argv[3], pin_len);
@@ -525,6 +717,26 @@ static void pin_reply_p(int argc, const char **argv)
EXEC(if_bluetooth->pin_reply, &addr, accept, pin_len, &pin);
}

+static void ssp_reply_c(int argc, const char **argv,
+ enum_func *penum_func, void **puser)
+{
+ if (argc == 3) {
+ *puser = last_remote_addr;
+ *penum_func = enum_one_string;
+ } else if (argc == 5) {
+ *puser = "1";
+ *penum_func = enum_one_string;
+ } else if (argc == 4) {
+ if (-1 != (int) last_ssp_variant) {
+ *puser = (void *) bt_ssp_variant_t2str(last_ssp_variant);
+ *penum_func = enum_one_string;
+ } else {
+ *puser = TYPE_ENUM(bt_ssp_variant_t);
+ *penum_func = enum_defines;
+ }
+ }
+}
+
static void ssp_reply_p(int argc, const char **argv)
{
bt_bdaddr_t addr;
@@ -533,12 +745,8 @@ static void ssp_reply_p(int argc, const char **argv)
int passkey;

RETURN_IF_NULL(if_bluetooth);
+ VERIFY_ADDR_ARG(2, &addr);

- if (argc < 3) {
- haltest_error("No address specified\n");
- return;
- }
- str2bt_bdaddr_t(argv[2], &addr);
if (argc < 4) {
haltest_error("No ssp variant specified\n");
return;
@@ -557,6 +765,29 @@ static void ssp_reply_p(int argc, const char **argv)
EXEC(if_bluetooth->ssp_reply, &addr, var, accept, passkey);
}

+static void get_profile_interface_c(int argc, const char **argv,
+ enum_func *penum_func, void **puser)
+{
+ static const char *const profile_ids[] = {
+ BT_PROFILE_HANDSFREE_ID,
+ BT_PROFILE_ADVANCED_AUDIO_ID,
+ BT_PROFILE_HEALTH_ID,
+ BT_PROFILE_SOCKETS_ID,
+ BT_PROFILE_HIDHOST_ID,
+ BT_PROFILE_PAN_ID,
+#if PLATFORM_SDK_VERSION >= 18
+ BT_PROFILE_GATT_ID,
+#endif
+ BT_PROFILE_AV_RC_ID,
+ NULL
+ };
+
+ if (argc == 3) {
+ *puser = (void *) profile_ids;
+ *penum_func = enum_strings;
+ }
+}
+
static void get_profile_interface_p(int argc, const char **argv)
{
const char *id = argv[2];
@@ -595,6 +826,10 @@ static void dut_mode_configure_p(int argc, const char **argv)

RETURN_IF_NULL(if_bluetooth);

+ if (argc <= 2) {
+ haltest_error("No dut mode specified\n");
+ return;
+ }
mode = strtol(argv[2], NULL, 0);

EXEC(if_bluetooth->dut_mode_configure, mode);
@@ -606,22 +841,22 @@ static struct method methods[] = {
STD_METHOD(enable),
STD_METHOD(disable),
STD_METHOD(get_adapter_properties),
- STD_METHOD(get_adapter_property),
- STD_METHOD(set_adapter_property),
- STD_METHOD(get_remote_device_properties),
- STD_METHOD(get_remote_device_property),
- STD_METHOD(set_remote_device_property),
- STD_METHOD(get_remote_service_record),
- STD_METHOD(get_remote_services),
+ STD_METHODCH(get_adapter_property, "<prop_type>"),
+ STD_METHODCH(set_adapter_property, "<prop_type> <prop_value>"),
+ STD_METHODCH(get_remote_device_properties, "<addr>"),
+ STD_METHODCH(get_remote_device_property, "<addr> <property_type>"),
+ STD_METHODCH(set_remote_device_property, "<addr> <property_type> <value>"),
+ STD_METHODCH(get_remote_service_record, "<addr> <uuid>"),
+ STD_METHODCH(get_remote_services, "<addr>"),
STD_METHOD(start_discovery),
STD_METHOD(cancel_discovery),
- STD_METHOD(create_bond),
- STD_METHOD(remove_bond),
- STD_METHOD(cancel_bond),
- STD_METHOD(pin_reply),
- STD_METHOD(ssp_reply),
- STD_METHOD(get_profile_interface),
- STD_METHOD(dut_mode_configure),
+ STD_METHODCH(create_bond, "<addr>"),
+ STD_METHODCH(remove_bond, "<addr>"),
+ STD_METHODCH(cancel_bond, "<addr>"),
+ STD_METHODCH(pin_reply, "<address> [<pin>]"),
+ STD_METHODCH(ssp_reply, "<address> <ssp_veriant> 1|0 [<passkey>]"),
+ STD_METHODCH(get_profile_interface, "<profile id>"),
+ STD_METHODH(dut_mode_configure, "<dut mode>"),
END_METHOD
};

diff --git a/android/client/if-main.h b/android/client/if-main.h
index 3fb3007..b6bbf05 100644
--- a/android/client/if-main.h
+++ b/android/client/if-main.h
@@ -103,6 +103,12 @@ int haltest_error(const char *format, ...);
int haltest_info(const char *format, ...);
int haltest_warn(const char *format, ...);

+/*
+ * Enumerator for discovered devices, to be used as tab completion enum_func
+ */
+const char *enum_devices(void *v, int i);
+void add_remote_device(const bt_bdaddr_t *addr);
+
/* Helper macro for executing function on interface and printing BT_STATUS */
#define EXEC(f, ...) \
{ \
@@ -119,3 +125,13 @@ int haltest_warn(const char *format, ...);

#define RETURN_IF_NULL(x) \
do { if (!x) { haltest_error("%s is NULL\n", #x); return; } } while (0)
+
+#define VERIFY_ADDR_ARG(n, adr) \
+ do { \
+ if (n < argc) \
+ str2bt_bdaddr_t(argv[n], adr); \
+ else { \
+ haltest_error("No address specified\n");\
+ return;\
+ } \
+ } while (0)
diff --git a/android/client/textconv.c b/android/client/textconv.c
index eebad70..3493b1c 100644
--- a/android/client/textconv.c
+++ b/android/client/textconv.c
@@ -203,3 +203,24 @@ void str2bt_uuid_t(const char *str, bt_uuid_t *uuid)
str += 2;
}
}
+
+const char *enum_defines(void *v, int i)
+{
+ const struct int2str *m = v;
+
+ return m[i].str != NULL ? m[i].str : NULL;
+}
+
+const char *enum_strings(void *v, int i)
+{
+ const char **m = v;
+
+ return m[i] != NULL ? m[i] : NULL;
+}
+
+const char *enum_one_string(void *v, int i)
+{
+ const char *m = v;
+
+ return (i == 0) && (m[0] != 0) ? m : NULL;
+}
diff --git a/android/client/textconv.h b/android/client/textconv.h
index 8fe976c..88da641 100644
--- a/android/client/textconv.h
+++ b/android/client/textconv.h
@@ -54,7 +54,11 @@ struct int2str {

int int2str_findint(int v, const struct int2str m[]);
int int2str_findstr(const char *str, const struct int2str m[]);
+const char *enum_defines(void *v, int i);
+const char *enum_strings(void *v, int i);
+const char *enum_one_string(void *v, int i);

+#define TYPE_ENUM(type) ((void *)&__##type##2str[0])
#define DECINTMAP(type) \
extern struct int2str __##type##2str[]; \
const char *type##2##str(type v); \
--
1.7.9.5


2013-10-18 13:15:59

by Jerzy Kasenberg

[permalink] [raw]
Subject: [PATCH 2/3] android: Fix error in draw line in haltest tool

This fixes small error that showed up when tab completion inserted
characters before end of line.
Cursor in line was not moved correctly.
---
android/client/terminal.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/android/client/terminal.c b/android/client/terminal.c
index 0421633..8dd3a25 100644
--- a/android/client/terminal.c
+++ b/android/client/terminal.c
@@ -140,7 +140,7 @@ void terminal_draw_command_line(void)
putchar('>');

/* move cursor to it's place */
- terminal_move_cursor(line_len - line_buf_ix);
+ terminal_move_cursor(line_buf_ix - line_len);
}

/* inserts string into command line at cursor position */
--
1.7.9.5


2013-10-18 13:15:58

by Jerzy Kasenberg

[permalink] [raw]
Subject: [PATCH 1/3] android: Add tab completion to haltest

This patch adds tab completion to interfaces, methods and parameters
in haltest.
---
Makefile.android | 2 +
android/Android.mk | 1 +
android/client/if-main.h | 28 +++-
android/client/tabcompletion.c | 313 ++++++++++++++++++++++++++++++++++++++++
android/client/terminal.c | 2 +-
5 files changed, 342 insertions(+), 4 deletions(-)
create mode 100644 android/client/tabcompletion.c

diff --git a/Makefile.android b/Makefile.android
index 56caf78..dcaca3c 100644
--- a/Makefile.android
+++ b/Makefile.android
@@ -42,6 +42,7 @@ android_haltest_SOURCES = android/client/haltest.c \
android/client/terminal.c \
android/client/history.c \
android/client/textconv.c \
+ android/client/tabcompletion.c \
android/client/if-bt.c \
android/client/hwmodule.c

@@ -67,6 +68,7 @@ EXTRA_DIST += android/client/terminal.c \
android/client/history.c \
android/client/if-bt.c \
android/client/textconv.c \
+ android/client/tabcompletion.c \
android/client/textconv.h \
android/client/if-main.h \
android/client/pollhandler.h \
diff --git a/android/Android.mk b/android/Android.mk
index 5858c3d..30b2169 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -78,6 +78,7 @@ LOCAL_SRC_FILES := \
client/terminal.c \
client/history.c \
client/textconv.c \
+ client/tabcompletion.c \
client/if-bt.c \

LOCAL_SHARED_LIBRARIES := libhardware
diff --git a/android/client/if-main.h b/android/client/if-main.h
index 9cac7ef..3fb3007 100644
--- a/android/client/if-main.h
+++ b/android/client/if-main.h
@@ -56,9 +56,12 @@ extern const struct interface bluetooth_if;
/* Interfaces that will show up in tool (first part of command line) */
extern const struct interface *interfaces[];

-#define METHOD(name, func) {name, func}
-#define STD_METHOD(m) {#m, m##_p}
-#define END_METHOD {"", NULL}
+#define METHOD(name, func, comp, help) {name, func, comp, help}
+#define STD_METHOD(m) {#m, m##_p, NULL, NULL}
+#define STD_METHODC(m) {#m, m##_p, m##_c, NULL}
+#define STD_METHODH(m, h) {#m, m##_p, NULL, h}
+#define STD_METHODCH(m, h) {#m, m##_p, m##_c, h}
+#define END_METHOD {"", NULL, NULL, NULL}

/*
* Function to parse argument for function, argv[0] and argv[1] are already
@@ -69,12 +72,31 @@ extern const struct interface *interfaces[];
typedef void (*parse_and_call)(int argc, const char **argv);

/*
+ * This is prototype of function that will return string for given number.
+ * Purpose is to enumerate string for auto completion.
+ * Function of this type will always be called in loop.
+ * First time function is called i = 0, then if function returns non-NULL
+ * it will be called again till for some value of i it will return NULL
+ */
+typedef const char *(*enum_func)(void *user, int i);
+
+/*
+ * This is prototype of function that when given argc, argv will
+ * fill penum_func with pointer to function that will enumerate
+ * parameters for argc argument, puser will be passed to penum_func.
+ */
+typedef void (*tab_complete)(int argc, const char **argv,
+ enum_func *penum_func, void **puser);
+
+/*
* For each method there is name and two functions to parse command line
* and call proper hal function on.
*/
struct method {
const char *name;
parse_and_call func;
+ tab_complete complete;
+ const char *help;
};

int haltest_error(const char *format, ...);
diff --git a/android/client/tabcompletion.c b/android/client/tabcompletion.c
new file mode 100644
index 0000000..e9c9921
--- /dev/null
+++ b/android/client/tabcompletion.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include "if-main.h"
+#include "terminal.h"
+
+/* how many times tab was hit */
+static int tab_hit_count;
+
+typedef struct split_arg {
+ struct split_arg *next; /* next argument in buffer */
+ const char *origin; /* pointer to original argument */
+ char ntcopy[1]; /* null terminated copy of argument */
+} split_arg_t;
+
+/* function returns interface of given name or NULL if not found */
+static const struct interface *get_interface(const char *name)
+{
+ int i;
+
+ for (i = 0; interfaces[i] != NULL; ++i) {
+ if (strcmp(interfaces[i]->name, name) == 0)
+ break;
+ }
+
+ return interfaces[i];
+}
+
+/* function returns method of given name or NULL if not found */
+static const struct method *get_method(const char *iname, const char *mname)
+{
+ int i;
+ const struct interface *iface = get_interface(iname);
+
+ if (iface == NULL)
+ return NULL;
+
+ for (i = 0; iface->methods[i].name[0]; ++i) {
+ if (0 == strcmp(iface->methods[i].name, mname))
+ return &iface->methods[i];
+ }
+ return NULL;
+}
+
+/* prints matching elements */
+static void print_matches(enum_func f, void *user, const char *prefix, int len)
+{
+ int i;
+ const char *enum_name;
+
+ putchar('\n');
+ for (i = 0; NULL != (enum_name = f(user, i)); ++i) {
+ if (strncmp(enum_name, prefix, len) == 0)
+ printf("%s\t", enum_name);
+ }
+ putchar('\n');
+ terminal_draw_command_line();
+}
+
+/*
+ * This function splits command line into linked list of arguments.
+ * line_buffer - pointer to input comman line
+ * size - size of command line to parse
+ * buf - output buffer to keep splited arguments list
+ * buf_size_in_bytes - size of buf
+ */
+static int split_command(const char *line_buffer, int size,
+ split_arg_t *buf, int buf_size_in_bytes)
+{
+ split_arg_t *prev = NULL;
+ split_arg_t *arg = buf;
+ int argc = 0;
+ const char *p = line_buffer;
+ const char *e = p + (size > 0 ? size : (int) strlen(p));
+ int len;
+
+ do {
+ while (p < e && isspace(*p))
+ p++;
+ arg->origin = p;
+ arg->next = NULL;
+ while (p < e && !isspace(*p))
+ p++;
+ len = p - arg->origin;
+ if (&arg->ntcopy[0] + len + 1 >
+ (const char *) buf + buf_size_in_bytes)
+ break;
+ strncpy(arg->ntcopy, arg->origin, len);
+ arg->ntcopy[len] = 0;
+ if (prev != NULL)
+ prev->next = arg;
+ prev = arg;
+ arg += (2 * sizeof(*arg) + len) / sizeof(*arg);
+ argc++;
+ } while (p < e);
+
+ return argc;
+}
+
+/* Function to enumerate interface names */
+static const char *interface_name(void *v, int i)
+{
+ return interfaces[i] ? interfaces[i]->name : NULL;
+}
+
+/* Function to enumerate method names */
+static const char *methods_name(void *v, int i)
+{
+ const struct interface *iface = v;
+
+ return iface->methods[i].name[0] ? iface->methods[i].name : NULL;
+}
+
+struct command_completion_args;
+typedef void (*short_help)(struct command_completion_args *args);
+
+struct command_completion_args {
+ const split_arg_t *arg; /* list of arguments */
+ const char *typed; /* last typed element */
+ enum_func func; /* enumerating function */
+ void *user; /* argument to enumerating function */
+ short_help help; /* help function */
+ void *user_help; /* additional data (used by short_help) */
+};
+
+/*
+ * complete command line
+ */
+static void command_completion(struct command_completion_args *args)
+{
+ const char *name = args->typed;
+ const int len = strlen(name);
+ int i;
+ int j;
+ char prefix[128] = {0};
+ int prefix_len = 0;
+ int count = 0;
+ const char *enum_name;
+
+ for (i = 0; NULL != (enum_name = args->func(args->user, i)); ++i) {
+ /* prefix does not match */
+ if (strncmp(enum_name, name, len) != 0)
+ continue;
+ /* prefix matches first time */
+ if (count++ == 0) {
+ strcpy(prefix, enum_name);
+ prefix_len = strlen(prefix);
+ continue;
+ }
+ /*
+ * Prefix matches next time
+ * reduce prefix to common part
+ */
+ for (j = 0; prefix[j] != 0
+ && prefix[j] == enum_name[j];)
+ ++j;
+ prefix_len = j;
+ prefix[j] = 0;
+ }
+
+ if (count == 0) {
+ /* no matches */
+ if (args->help != NULL)
+ args->help(args);
+ tab_hit_count = 0;
+ return;
+ }
+ /* len == prefix_len => nothing new was added */
+ if (len == prefix_len) {
+ if (count != 1) {
+ if (tab_hit_count == 1)
+ putchar('\a');
+ else if (tab_hit_count == 2 ||
+ args->help == NULL) {
+ print_matches(args->func,
+ args->user, name, len);
+ } else {
+ args->help(args);
+ tab_hit_count = 1;
+ }
+ } else if (count == 1) {
+ /* nothing to add, exact match add space */
+ terminal_insert_into_command_line(" ");
+ }
+ } else {
+ /* new chars can be added from some interface name(s) */
+ if (count == 1) {
+ /* exact match, add space */
+ prefix[prefix_len++] = ' ';
+ prefix[prefix_len] = '\0';
+ }
+ terminal_insert_into_command_line(prefix + len);
+ tab_hit_count = 0;
+ }
+}
+
+/* interface completion */
+static void interface_completion(split_arg_t *arg)
+{
+ struct command_completion_args args = {
+ .arg = arg,
+ .typed = arg->ntcopy,
+ .func = interface_name
+ };
+
+ command_completion(&args);
+}
+
+/* method completion */
+static void method_completion(const struct interface *iface, split_arg_t *arg)
+{
+ struct command_completion_args args = {
+ .arg = arg,
+ .typed = arg->next->ntcopy,
+ .func = methods_name,
+ .user = (void *) iface
+ };
+
+ if (iface == NULL)
+ return;
+
+ command_completion(&args);
+}
+
+/* prints short help on method for interface */
+static void method_help(struct command_completion_args *args)
+{
+ if (args->user_help == NULL)
+ return;
+
+ haltest_info("%s %s %s\n", args->arg->ntcopy,
+ args->arg->next->ntcopy, args->user_help);
+}
+
+/* So we have empty enumeration */
+static const char *return_null(void *user, int i)
+{
+ return NULL;
+}
+
+/* parameter completion function */
+static void param_completion(int argc, const split_arg_t *arg)
+{
+ const struct method *method;
+ int i;
+ const char *argv[argc];
+ const split_arg_t *tmp = arg;
+ struct command_completion_args args = {
+ .arg = arg,
+ .func = return_null
+ };
+
+ /* prepare standard argv from arg */
+ for (i = 0; i < argc; ++i) {
+ argv[i] = tmp->ntcopy;
+ tmp = tmp->next;
+ }
+
+ /* Find method for <interface, name> pair */
+ method = get_method(argv[0], argv[1]);
+
+ if (method != NULL && method->complete != NULL) {
+ /* ask method for completion function */
+ method->complete(argc, argv, &args.func, &args.user);
+ }
+
+ /* If method provided enumeration function call try to complete */
+ if (args.func != NULL) {
+ args.typed = argv[argc - 1];
+ args.help = method_help;
+ args.user_help = (void *) method->help;
+
+ command_completion(&args);
+ }
+}
+
+/*
+ * This methd gets called when user tapped tab key.
+ * line - points to comman line
+ * len - size of line that should be used for comletions. This should be
+ * cursor position during tab hit.
+ */
+void process_tab(const char *line, int len)
+{
+ int argc;
+ static split_arg_t buf[(LINE_BUF_MAX * 2) / sizeof(split_arg_t)];
+
+ argc = split_command(line, len, buf, sizeof(buf));
+ tab_hit_count++;
+
+ if (argc == 1)
+ interface_completion(buf);
+ else if (argc == 2)
+ method_completion(get_interface(buf[0].ntcopy), buf);
+ else if (argc > 2)
+ param_completion(argc, buf);
+}
diff --git a/android/client/terminal.c b/android/client/terminal.c
index b484ef6..0421633 100644
--- a/android/client/terminal.c
+++ b/android/client/terminal.c
@@ -453,7 +453,7 @@ void terminal_process_char(int c, void (*process_line)(char *line))
break;
case '\t':
/* tab processing */
- /* TODO Add completion here */
+ process_tab(line_buf, line_buf_ix);
break;
case KEY_BACKSPACE:
if (line_buf_ix <= 0)
--
1.7.9.5