mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05: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
					
				
					 3 changed files with 100 additions and 35 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue