mirror of
https://gitlab.freedesktop.org/pipewire/pipewire
synced 2024-10-04 15:10:20 +00:00
pw-cli: use readline() in interactive mode
With history and a simple command completion hook this makes the interactive mode a lot easier to deal with.
This commit is contained in:
parent
7d58ce9e24
commit
ae59185f6f
|
@ -325,6 +325,7 @@ pthread_lib = dependency('threads')
|
|||
dbus_dep = dependency('dbus-1')
|
||||
sdl_dep = dependency('sdl2', required : get_option('sdl2'))
|
||||
summary({'SDL 2': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies')
|
||||
readline_dep = dependency('readline', required : false)
|
||||
ncurses_dep = dependency('ncursesw', required : false)
|
||||
sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile'))
|
||||
summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain')
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
tools_sources = [
|
||||
[ 'pw-mon', [ 'pw-mon.c' ] ],
|
||||
[ 'pw-cli', [ 'pw-cli.c' ] ],
|
||||
[ 'pw-dot', [ 'pw-dot.c' ] ],
|
||||
[ 'pw-dump', [ 'pw-dump.c' ] ],
|
||||
[ 'pw-profiler', [ 'pw-profiler.c' ] ],
|
||||
|
@ -18,6 +17,14 @@ foreach t : tools_sources
|
|||
)
|
||||
endforeach
|
||||
|
||||
if readline_dep.found()
|
||||
executable('pw-cli',
|
||||
'pw-cli.c',
|
||||
install: true,
|
||||
dependencies: [pipewire_dep, readline_dep]
|
||||
)
|
||||
endif
|
||||
|
||||
if ncurses_dep.found()
|
||||
executable('pw-top',
|
||||
'pw-top.c',
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#include <alloca.h>
|
||||
#endif
|
||||
#include <getopt.h>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
|
||||
#define spa_debug(...) fprintf(stdout,__VA_ARGS__);fputc('\n', stdout)
|
||||
|
||||
|
@ -48,6 +50,7 @@
|
|||
#include <pipewire/extensions/session-manager.h>
|
||||
|
||||
static const char WHITESPACE[] = " \t";
|
||||
static char prompt[64];
|
||||
|
||||
struct remote_data;
|
||||
|
||||
|
@ -279,11 +282,10 @@ static void on_core_info(void *_data, const struct pw_core_info *info)
|
|||
fprintf(stdout, "remote %d is named '%s'\n", rd->id, rd->name);
|
||||
}
|
||||
|
||||
static void show_prompt(struct remote_data *rd)
|
||||
static void set_prompt(struct remote_data *rd)
|
||||
{
|
||||
rd->data->monitoring = true;
|
||||
fprintf(stdout, "%s>>", rd->name);
|
||||
fflush(stdout);
|
||||
snprintf(prompt, sizeof(prompt), "%s>> ", rd->name);
|
||||
rl_set_prompt(prompt);
|
||||
}
|
||||
|
||||
static void on_core_done(void *_data, uint32_t id, int seq)
|
||||
|
@ -292,10 +294,12 @@ static void on_core_done(void *_data, uint32_t id, int seq)
|
|||
struct data *d = rd->data;
|
||||
|
||||
if (seq == rd->prompt_pending) {
|
||||
if (d->interactive)
|
||||
show_prompt(rd);
|
||||
else
|
||||
if (d->interactive) {
|
||||
set_prompt(rd);
|
||||
rd->data->monitoring = true;
|
||||
} else {
|
||||
pw_main_loop_quit(d->loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2880,7 +2884,7 @@ usage:
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool parse(struct data *data, char *buf, size_t size, char **error)
|
||||
static bool parse(struct data *data, char *buf, char **error)
|
||||
{
|
||||
char *a[2];
|
||||
int n;
|
||||
|
@ -2912,35 +2916,35 @@ static bool parse(struct data *data, char *buf, size_t size, char **error)
|
|||
return false;
|
||||
}
|
||||
|
||||
static void do_input(void *data, int fd, uint32_t mask)
|
||||
/* We need a global variable, readline doesn't have a closure arg */
|
||||
static struct data *readline_dataptr;
|
||||
|
||||
static void readline_process_line(char *line)
|
||||
{
|
||||
struct data *d = data;
|
||||
char buf[4096], *error;
|
||||
ssize_t r;
|
||||
struct data *d = readline_dataptr;
|
||||
char *error;
|
||||
|
||||
if (mask & SPA_IO_IN) {
|
||||
while (true) {
|
||||
r = read(fd, buf, sizeof(buf)-1);
|
||||
if (r < 0) {
|
||||
if (errno == EAGAIN)
|
||||
continue;
|
||||
perror("read");
|
||||
r = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (r == 0) {
|
||||
fprintf(stdout, "\n");
|
||||
pw_main_loop_quit(d->loop);
|
||||
return;
|
||||
}
|
||||
buf[r] = '\0';
|
||||
if (!line)
|
||||
line = strdup("quit");
|
||||
|
||||
if (!parse(d, buf, r, &error)) {
|
||||
if (line[0] != '\0') {
|
||||
add_history(line);
|
||||
if (!parse(d, line, &error)) {
|
||||
fprintf(stdout, "Error: \"%s\"\n", error);
|
||||
free(error);
|
||||
}
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
|
||||
static void do_input(void *data, int fd, uint32_t mask)
|
||||
{
|
||||
struct data *d = data;
|
||||
|
||||
if (mask & SPA_IO_IN) {
|
||||
readline_dataptr = d;
|
||||
rl_callback_read_char();
|
||||
|
||||
if (d->current == NULL)
|
||||
pw_main_loop_quit(d->loop);
|
||||
else {
|
||||
|
@ -2951,6 +2955,55 @@ static void do_input(void *data, int fd, uint32_t mask)
|
|||
}
|
||||
}
|
||||
|
||||
static char *
|
||||
readline_match_command(const char *text, int state)
|
||||
{
|
||||
static size_t idx;
|
||||
static int len;
|
||||
|
||||
if (!state) {
|
||||
idx = 0;
|
||||
len = strlen(text);
|
||||
}
|
||||
|
||||
while (idx < SPA_N_ELEMENTS(command_list)) {
|
||||
const char *name = command_list[idx].name;
|
||||
const char *alias = command_list[idx].alias;
|
||||
|
||||
idx++;
|
||||
if (spa_strneq(name, text, len) || spa_strneq(alias, text, len))
|
||||
return strdup(name);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char **
|
||||
readline_command_completion(const char *text, int start, int end)
|
||||
{
|
||||
char **matches = NULL;
|
||||
|
||||
/* Only try to complete the first word in a line */
|
||||
if (start == 0)
|
||||
matches = rl_completion_matches(text, readline_match_command);
|
||||
|
||||
/* Don't fall back to filename completion */
|
||||
rl_attempted_completion_over = true;
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
static void readline_init()
|
||||
{
|
||||
rl_attempted_completion_function = readline_command_completion;
|
||||
rl_callback_handler_install(">> ", readline_process_line);
|
||||
}
|
||||
|
||||
static void readline_cleanup()
|
||||
{
|
||||
rl_callback_handler_remove();
|
||||
}
|
||||
|
||||
static void do_quit_on_signal(void *data, int signal_number)
|
||||
{
|
||||
struct data *d = data;
|
||||
|
@ -3046,12 +3099,16 @@ int main(int argc, char *argv[])
|
|||
if (optind == argc) {
|
||||
data.interactive = true;
|
||||
|
||||
pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data);
|
||||
|
||||
fprintf(stdout, "Welcome to PipeWire version %s. Type 'help' for usage.\n",
|
||||
pw_get_library_version());
|
||||
|
||||
readline_init();
|
||||
|
||||
pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data);
|
||||
|
||||
pw_main_loop_run(data.loop);
|
||||
|
||||
readline_cleanup();
|
||||
} else {
|
||||
char buf[4096], *p, *error;
|
||||
|
||||
|
@ -3063,7 +3120,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
pw_main_loop_run(data.loop);
|
||||
|
||||
if (!parse(&data, buf, p - buf, &error)) {
|
||||
if (!parse(&data, buf, &error)) {
|
||||
fprintf(stdout, "Error: \"%s\"\n", error);
|
||||
free(error);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue