1
0
Fork 0
mirror of https://github.com/albfan/miraclecast.git synced 2025-02-12 19:41:53 +00:00

CLI commands autocompletion

This commit is contained in:
Alberto Fanjul 2022-11-13 11:29:16 +01:00
parent 20816ad138
commit 788d37d7d2
4 changed files with 386 additions and 23 deletions

View file

@ -36,9 +36,6 @@
#include <math.h> #include <math.h>
#include <time.h> #include <time.h>
/* *sigh* readline doesn't include all their deps, so put them last */
#include <readline/history.h>
#include <readline/readline.h>
/* /*
* Helpers for interactive commands * Helpers for interactive commands
@ -246,7 +243,10 @@ static void cli_handler_fn(char *input)
else if (!r) else if (!r)
return; return;
add_history(original); if (!(strcmp(original, "quit") == 0 || strcmp(original, "exit") == 0)) {
add_history(original);
write_history(get_history_filename());
}
r = cli_do(cli_cmds, args, r); r = cli_do(cli_cmds, args, r);
if (r != -EAGAIN) if (r != -EAGAIN)
return; return;
@ -325,6 +325,315 @@ void cli_destroy(void)
cli_event = NULL; cli_event = NULL;
} }
char *yes_no_options[] = {"yes", "no", NULL};
char *
yes_no_generator (const char *text, int state)
{
static int list_index, len;
char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the index
variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
/* Return the next name which partially matches from the command list. */
while (name = yes_no_options[list_index])
{
list_index++;
if (strncmp (name, text, len) == 0)
return (strdup(name));
}
/* If no names matched, then return NULL. */
return ((char *)NULL);
}
char *
links_peers_generator (const char *text, int state)
{
static int list_index, len;
char *name;
size_t peer_cnt = 0;
size_t link_cnt = 0;
struct shl_dlist *i, *j;
struct ctl_link *l;
struct ctl_peer *p;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the index
variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
shl_dlist_for_each(i, &get_wifi()->links) {
l = link_from_dlist(i);
char *name = l->label;
if (strncmp (name, text, len) == 0)
{
if (link_cnt == list_index)
{
list_index++;
return strdup(name);
}
link_cnt++;
}
name = l->friendly_name;
if (!shl_isempty(name))
{
if (strncmp (name, text, len) == 0)
{
if (link_cnt == list_index)
{
list_index++;
return strdup(name);
}
link_cnt++;
}
}
}
peer_cnt = link_cnt;
shl_dlist_for_each(i, &get_wifi()->links) {
l = link_from_dlist(i);
shl_dlist_for_each(j, &l->peers) {
p = peer_from_dlist(j);
char *name = p->label;
if (strncmp (name, text, len) == 0)
{
if (peer_cnt == list_index)
{
list_index++;
return strdup(name);
}
peer_cnt++;
}
name = p->friendly_name;
if (!shl_isempty(name))
{
if (strncmp (name, text, len) == 0)
{
if (peer_cnt == list_index)
{
list_index++;
return strdup(name);
}
peer_cnt++;
}
}
}
}
/* If no names matched, then return NULL. */
return ((char *)NULL);
}
char *
peers_generator (const char *text, int state)
{
static int list_index, len;
char *name;
size_t peer_cnt = 0;
struct shl_dlist *i, *j;
struct ctl_link *l;
struct ctl_peer *p;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the index
variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
shl_dlist_for_each(i, &get_wifi()->links) {
l = link_from_dlist(i);
shl_dlist_for_each(j, &l->peers) {
p = peer_from_dlist(j);
char *name = p->label;
if (strncmp (name, text, len) == 0)
{
if (peer_cnt == list_index)
{
list_index++;
return strdup(name);
}
peer_cnt++;
}
name = p->friendly_name;
if (!shl_isempty(name))
{
if (strncmp (name, text, len) == 0)
{
if (peer_cnt == list_index)
{
list_index++;
return strdup(name);
}
peer_cnt++;
}
}
}
}
/* If no names matched, then return NULL. */
return ((char *)NULL);
}
char *
links_generator (const char *text, int state)
{
static int list_index, len;
char *name;
size_t link_cnt = 0;
struct shl_dlist *i;
struct ctl_link *l;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the index
variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
shl_dlist_for_each(i, &get_wifi()->links) {
l = link_from_dlist(i);
char *name = l->label;
if (strncmp (name, text, len) == 0)
{
if (link_cnt == list_index)
{
list_index++;
return strdup(name);
}
link_cnt++;
}
name = l->friendly_name;
if (!shl_isempty(name))
{
if (strncmp (name, text, len) == 0)
{
if (link_cnt == list_index)
{
list_index++;
return strdup(name);
}
link_cnt++;
}
}
}
/* If no names matched, then return NULL. */
return ((char *)NULL);
}
/* Generator function for command completion. STATE lets us know whether
* to start from scratch; without any state (i.e. STATE == 0), then we
* start at the top of the list.
*/
char *
command_generator (const char *text, int state)
{
static int list_index, len;
char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the index
variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
/* Return the next name which partially matches from the command list. */
while (name = cli_cmds[list_index].cmd)
{
list_index++;
if (strncmp (name, text, len) == 0)
return (strdup(name));
}
/* If no names matched, then return NULL. */
return ((char *)NULL);
}
int get_args(char* line)
{
char* tmp = line;
char* last_delim = tmp;
int count = 0;
/* Count how many elements will be extracted. */
while (*tmp)
{
if (' ' == *tmp)
{
if (last_delim+1 < tmp)
count++;
last_delim = tmp;
}
tmp++;
}
if (" " != *last_delim)
count++;
return count;
}
/*
* Attempt to complete on the contents of TEXT. START and END bound the
* region of rl_line_buffer that contains the word to complete. TEXT is
* the word to complete. We can use the entire contents of rl_line_buffer
* in case we want to do some simple parsing. Return the array of matches,
* or NULL if there aren't any.
*/
char **
completion_fn (const char *text, int start, int end)
{
char **matches;
rl_attempted_completion_over = 1;
if (start == 0)
matches = rl_completion_matches (text, command_generator);
else
{
matches = (char **)NULL;
struct cli_cmd cmd;
int cmd_pos = 0;
while ((cmd = cli_cmds[cmd_pos++]).cmd)
{
if (strncmp(cmd.cmd, rl_line_buffer, strlen(cmd.cmd)) == 0)
{
int nargs = get_args(rl_line_buffer);
rl_compentry_func_t* completion_fn = cmd.completion_fns[nargs-2];
if (completion_fn)
matches = rl_completion_matches (text, completion_fn);
}
}
}
return (matches);
}
int cli_init(sd_bus *bus, const struct cli_cmd *cmds) int cli_init(sd_bus *bus, const struct cli_cmd *cmds)
{ {
static const int sigs[] = { static const int sigs[] = {
@ -382,7 +691,11 @@ int cli_init(sd_bus *bus, const struct cli_cmd *cmds)
cli_rl = true; cli_rl = true;
rl_erase_empty_line = 1; rl_erase_empty_line = 1;
rl_attempted_completion_function = completion_fn;
rl_callback_handler_install(NULL, cli_handler_fn); rl_callback_handler_install(NULL, cli_handler_fn);
using_history();
read_history(get_history_filename());
rl_end_of_history(0, 0);
rl_set_prompt(CLI_PROMPT); rl_set_prompt(CLI_PROMPT);
printf("\r"); printf("\r");

View file

@ -29,6 +29,11 @@
#include "shl_dlist.h" #include "shl_dlist.h"
#include "shl_log.h" #include "shl_log.h"
/* *sigh* readline doesn't include all their deps, so put them last */
#include <readline/history.h>
#include <readline/readline.h>
#include <readline/rltypedefs.h>
#ifndef CTL_CTL_H #ifndef CTL_CTL_H
#define CTL_CTL_H #define CTL_CTL_H
@ -36,6 +41,13 @@ struct ctl_wifi;
struct ctl_link; struct ctl_link;
struct ctl_peer; struct ctl_peer;
char* get_history_filename ();
struct ctl_wifi * get_wifi ();
char * links_peers_generator (const char *text, int state);
char * links_generator (const char *text, int state);
char * peers_generator (const char *text, int state);
char * yes_no_generator (const char *text, int state);
/* wifi handling */ /* wifi handling */
struct ctl_peer { struct ctl_peer {
@ -209,6 +221,7 @@ struct cli_cmd {
int argc; int argc;
int (*fn) (char **args, unsigned int n); int (*fn) (char **args, unsigned int n);
const char *desc; const char *desc;
rl_compentry_func_t *completion_fns[2];
}; };
extern sd_event *cli_event; extern sd_event *cli_event;

View file

@ -47,6 +47,10 @@
#include "util.h" #include "util.h"
#include "config.h" #include "config.h"
#include <readline/readline.h>
#define HISTORY_FILENAME ".miracle-sink.history"
static sd_bus *bus; static sd_bus *bus;
static struct ctl_wifi *wifi; static struct ctl_wifi *wifi;
static struct ctl_sink *sink; static struct ctl_sink *sink;
@ -78,6 +82,21 @@ unsigned int wfd_supported_res_cea = 0x0001ffff;
unsigned int wfd_supported_res_vesa = 0x1fffffff; unsigned int wfd_supported_res_vesa = 0x1fffffff;
unsigned int wfd_supported_res_hh = 0x00001fff; unsigned int wfd_supported_res_hh = 0x00001fff;
struct ctl_wifi *get_wifi()
{
return wifi;
}
/*
* get history filename
*/
char* get_history_filename()
{
return HISTORY_FILENAME;
}
/* /*
* cmd list * cmd list
*/ */
@ -412,15 +431,15 @@ static int sink_timeout_fn(sd_event_source *s, uint64_t usec, void *data)
} }
static const struct cli_cmd cli_cmds[] = { static const struct cli_cmd cli_cmds[] = {
{ "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects", {NULL} },
{ "show", "<link|peer>", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, { "show", "<link|peer>", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information", {links_peers_generator, NULL} },
{ "run", "<link>", CLI_M, CLI_EQUAL, 1, cmd_run, "Run sink on given link" }, { "run", "<link>", CLI_M, CLI_EQUAL, 1, cmd_run, "Run sink on given link", {links_generator, NULL} },
{ "bind", "<link>", CLI_M, CLI_EQUAL, 1, cmd_bind, "Like 'run' but bind the link name to run when it is hotplugged" }, { "bind", "<link>", CLI_M, CLI_EQUAL, 1, cmd_bind, "Like 'run' but bind the link name to run when it is hotplugged", {links_generator, NULL} },
{ "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" }, { "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object", {links_generator, NULL} },
{ "set-managed", "<link> <yes|no>", CLI_M, CLI_EQUAL, 2, cmd_set_managed, "Manage or unmnage a link" }, { "set-managed", "<link> <yes|no>", CLI_M, CLI_EQUAL, 2, cmd_set_managed, "Manage or unmnage a link", {links_generator, yes_no_generator, NULL} },
{ "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program", {NULL} },
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL, {NULL} },
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help", {NULL} },
{ }, { },
}; };

View file

@ -34,11 +34,29 @@
#include "util.h" #include "util.h"
#include "config.h" #include "config.h"
#include <readline/readline.h>
#define HISTORY_FILENAME ".miracle-wifi.history"
static sd_bus *bus; static sd_bus *bus;
static struct ctl_wifi *wifi; static struct ctl_wifi *wifi;
static struct ctl_link *selected_link; static struct ctl_link *selected_link;
/*
* get history filename
*/
char* get_history_filename()
{
return HISTORY_FILENAME;
}
struct ctl_wifi *get_wifi()
{
return wifi;
}
/* /*
* cmd list * cmd list
*/ */
@ -386,17 +404,17 @@ static int cmd_quit(char **args, unsigned int n)
*/ */
static const struct cli_cmd cli_cmds[] = { static const struct cli_cmd cli_cmds[] = {
{ "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" }, { "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects", {NULL}},
{ "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link" }, { "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link", {links_generator, NULL} },
{ "show", "[link|peer]", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" }, { "show", "[link|peer]", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information", {links_peers_generator, NULL} },
{ "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" }, { "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object", {links_generator, yes_no_generator, NULL} },
{ "set-managed", "[link] <yes|no>", CLI_M, CLI_LESS, 2, cmd_set_managed, "Manage or unmnage a link" }, { "set-managed", "[link] <yes|no>", CLI_M, CLI_LESS, 2, cmd_set_managed, "Manage or unmnage a link" },
{ "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning" }, { "p2p-scan", "[link] [stop]", CLI_Y, CLI_LESS, 2, cmd_p2p_scan, "Control neighborhood P2P scanning", {links_generator, NULL} },
{ "connect", "<peer> [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer" }, { "connect", "<peer> [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer", {peers_generator, NULL} },
{ "disconnect", "<peer>", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer" }, { "disconnect", "<peer>", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer", {peers_generator, NULL} },
{ "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" }, { "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program", {NULL} },
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL }, { "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL , {NULL}},
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" }, { "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" , {NULL} },
{ }, { },
}; };