mirror of
https://github.com/albfan/miraclecast.git
synced 2025-02-12 14:11:56 +00:00
CLI commands autocompletion
This commit is contained in:
parent
20816ad138
commit
788d37d7d2
4 changed files with 386 additions and 23 deletions
|
@ -36,9 +36,6 @@
|
|||
#include <math.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
|
||||
|
@ -246,7 +243,10 @@ static void cli_handler_fn(char *input)
|
|||
else if (!r)
|
||||
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);
|
||||
if (r != -EAGAIN)
|
||||
return;
|
||||
|
@ -325,6 +325,315 @@ void cli_destroy(void)
|
|||
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)
|
||||
{
|
||||
static const int sigs[] = {
|
||||
|
@ -382,7 +691,11 @@ int cli_init(sd_bus *bus, const struct cli_cmd *cmds)
|
|||
cli_rl = true;
|
||||
|
||||
rl_erase_empty_line = 1;
|
||||
rl_attempted_completion_function = completion_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);
|
||||
printf("\r");
|
||||
|
|
|
@ -29,6 +29,11 @@
|
|||
#include "shl_dlist.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
|
||||
#define CTL_CTL_H
|
||||
|
||||
|
@ -36,6 +41,13 @@ struct ctl_wifi;
|
|||
struct ctl_link;
|
||||
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 */
|
||||
|
||||
struct ctl_peer {
|
||||
|
@ -209,6 +221,7 @@ struct cli_cmd {
|
|||
int argc;
|
||||
int (*fn) (char **args, unsigned int n);
|
||||
const char *desc;
|
||||
rl_compentry_func_t *completion_fns[2];
|
||||
};
|
||||
|
||||
extern sd_event *cli_event;
|
||||
|
|
|
@ -47,6 +47,10 @@
|
|||
#include "util.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <readline/readline.h>
|
||||
|
||||
#define HISTORY_FILENAME ".miracle-sink.history"
|
||||
|
||||
static sd_bus *bus;
|
||||
static struct ctl_wifi *wifi;
|
||||
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_hh = 0x00001fff;
|
||||
|
||||
struct ctl_wifi *get_wifi()
|
||||
{
|
||||
return wifi;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get history filename
|
||||
*/
|
||||
|
||||
char* get_history_filename()
|
||||
{
|
||||
return HISTORY_FILENAME;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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[] = {
|
||||
{ "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" },
|
||||
{ "show", "<link|peer>", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" },
|
||||
{ "run", "<link>", CLI_M, CLI_EQUAL, 1, cmd_run, "Run sink on given link" },
|
||||
{ "bind", "<link>", CLI_M, CLI_EQUAL, 1, cmd_bind, "Like 'run' but bind the link name to run when it is hotplugged" },
|
||||
{ "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" },
|
||||
{ "set-managed", "<link> <yes|no>", CLI_M, CLI_EQUAL, 2, cmd_set_managed, "Manage or unmnage a link" },
|
||||
{ "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" },
|
||||
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL },
|
||||
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" },
|
||||
{ "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", {links_peers_generator, NULL} },
|
||||
{ "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", {links_generator, NULL} },
|
||||
{ "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", {links_generator, yes_no_generator, NULL} },
|
||||
{ "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program", {NULL} },
|
||||
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL, {NULL} },
|
||||
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help", {NULL} },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
|
|
@ -34,11 +34,29 @@
|
|||
#include "util.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <readline/readline.h>
|
||||
|
||||
#define HISTORY_FILENAME ".miracle-wifi.history"
|
||||
|
||||
static sd_bus *bus;
|
||||
static struct ctl_wifi *wifi;
|
||||
|
||||
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
|
||||
*/
|
||||
|
@ -386,17 +404,17 @@ static int cmd_quit(char **args, unsigned int n)
|
|||
*/
|
||||
|
||||
static const struct cli_cmd cli_cmds[] = {
|
||||
{ "list", NULL, CLI_M, CLI_LESS, 0, cmd_list, "List all objects" },
|
||||
{ "select", "[link]", CLI_Y, CLI_LESS, 1, cmd_select, "Select default link" },
|
||||
{ "show", "[link|peer]", CLI_M, CLI_LESS, 1, cmd_show, "Show detailed object information" },
|
||||
{ "set-friendly-name", "[link] <name>", CLI_M, CLI_LESS, 2, cmd_set_friendly_name, "Set friendly name of an object" },
|
||||
{ "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", {links_generator, NULL} },
|
||||
{ "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", {links_generator, yes_no_generator, NULL} },
|
||||
{ "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" },
|
||||
{ "connect", "<peer> [provision] [pin]", CLI_M, CLI_LESS, 3, cmd_connect, "Connect to peer" },
|
||||
{ "disconnect", "<peer>", CLI_M, CLI_EQUAL, 1, cmd_disconnect, "Disconnect from peer" },
|
||||
{ "quit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, "Quit program" },
|
||||
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL },
|
||||
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" },
|
||||
{ "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", {peers_generator, NULL} },
|
||||
{ "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", {NULL} },
|
||||
{ "exit", NULL, CLI_Y, CLI_MORE, 0, cmd_quit, NULL , {NULL}},
|
||||
{ "help", NULL, CLI_M, CLI_MORE, 0, NULL, "Print help" , {NULL} },
|
||||
{ },
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue