iwd/client/display.c

639 lines
12 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2017 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ell/ell.h>
#include <readline/history.h>
#include <readline/readline.h>
#include <stdio.h>
#include "agent.h"
#include "command.h"
#include "display.h"
#define IWD_PROMPT COLOR_GREEN "[iwd]" COLOR_OFF "# "
#define LINE_LEN 81
static struct l_signal *resize_signal;
static struct l_io *io;
static char dashed_line[LINE_LEN];
static char empty_line[LINE_LEN];
static struct l_timeout *refresh_timeout;
static struct saved_input *agent_saved_input;
static struct display_refresh {
char *family;
char *entity;
const struct command *cmd;
char **argv;
int argc;
size_t undo_lines;
struct l_queue *redo_entries;
bool recording;
} display_refresh;
struct saved_input {
char *line;
int point;
};
static struct saved_input *save_input(void)
{
struct saved_input *input;
if (RL_ISSTATE(RL_STATE_DONE))
return NULL;
input = l_new(struct saved_input, 1);
input->point = rl_point;
input->line = rl_copy_text(0, rl_end);
rl_save_prompt();
rl_replace_line("", 0);
rl_redisplay();
return input;
}
static void restore_input(struct saved_input *input)
{
if (!input)
return;
rl_restore_prompt();
rl_replace_line(input->line, 0);
rl_point = input->point;
rl_forced_update_display();
l_free(input->line);
l_free(input);
}
static void display_refresh_undo_lines(void)
{
size_t num_lines = display_refresh.undo_lines;
printf("\033[%dA", (int) num_lines);
do {
printf("%s\n", empty_line);
} while (--display_refresh.undo_lines);
printf("\033[%dA", (int) num_lines);
}
static void display_refresh_redo_lines(void)
{
const struct l_queue_entry *entry;
struct saved_input *input;
input = save_input();
for (entry = l_queue_get_entries(display_refresh.redo_entries); entry;
entry = entry->next) {
char *line = entry->data;
printf("%s", line);
display_refresh.undo_lines++;
}
restore_input(input);
display_refresh.recording = true;
l_timeout_modify(refresh_timeout, 1);
}
void display_refresh_reset(void)
{
l_free(display_refresh.family);
display_refresh.family = NULL;
l_free(display_refresh.entity);
display_refresh.entity = NULL;
display_refresh.cmd = NULL;
l_strfreev(display_refresh.argv);
display_refresh.argv = NULL;
display_refresh.argc = 0;
display_refresh.undo_lines = 0;
display_refresh.recording = false;
l_queue_clear(display_refresh.redo_entries, l_free);
}
void display_refresh_set_cmd(const char *family, const char *entity,
const struct command *cmd,
char **argv, int argc)
{
int i;
if (cmd->refreshable) {
l_free(display_refresh.family);
display_refresh.family = l_strdup(family);
l_free(display_refresh.entity);
display_refresh.entity = l_strdup(entity);
display_refresh.cmd = cmd;
l_strfreev(display_refresh.argv);
display_refresh.argc = argc;
display_refresh.argv = l_new(char *, argc + 1);
for (i = 0; i < argc; i++)
display_refresh.argv[i] = argv[i];
l_queue_clear(display_refresh.redo_entries, l_free);
display_refresh.recording = false;
display_refresh.undo_lines = 0;
return;
}
if (display_refresh.family && !strcmp(display_refresh.family, family)) {
struct l_string *buf = l_string_new(128);
L_AUTO_FREE_VAR(char *, args);
char *prompt;
for (i = 0; i < argc; i++)
l_string_append_printf(buf, "'%s' ", argv[i]);
args = l_string_unwrap(buf);
prompt = l_strdup_printf(IWD_PROMPT"%s%s%s %s %s\n",
family ? : "",
entity ? " " : "", entity ? : "",
cmd->cmd ? : "", args ? : "");
l_queue_push_tail(display_refresh.redo_entries, prompt);
display_refresh.undo_lines++;
display_refresh.recording = true;
} else {
display_refresh_reset();
}
}
static void timeout_callback(struct l_timeout *timeout, void *user_data)
{
struct saved_input *input;
if (!display_refresh.cmd)
return;
input = save_input();
display_refresh_undo_lines();
restore_input(input);
display_refresh.recording = false;
display_refresh.cmd->function(display_refresh.entity,
display_refresh.argv,
display_refresh.argc);
}
void display_refresh_timeout_set(void)
{
if (refresh_timeout)
l_timeout_modify(refresh_timeout, 1);
else
refresh_timeout = l_timeout_create(1, timeout_callback,
NULL, NULL);
}
static void display_text(const char *text)
{
struct saved_input *input = save_input();
printf("%s", text);
restore_input(input);
if (!display_refresh.cmd)
return;
display_refresh.undo_lines++;
if (display_refresh.recording)
l_queue_push_tail(display_refresh.redo_entries, l_strdup(text));
}
void display(const char *fmt, ...)
{
va_list args;
char *text;
va_start(args, fmt);
text = l_strdup_vprintf(fmt, args);
va_end(args);
display_text(text);
l_free(text);
}
void display_error(const char *error)
{
char *text = l_strdup_printf(COLOR_RED "%s\n" COLOR_OFF, error);
display_text(text);
l_free(text);
}
static char get_flasher(void)
{
static char c;
if (c == ' ')
c = '*';
else
c = ' ';
return c;
}
void display_table_header(const char *caption, const char *fmt, ...)
{
va_list args;
char *text;
char *body;
int caption_pos =
(int) ((sizeof(dashed_line) - 1) / 2 + strlen(caption) / 2);
text = l_strdup_printf("%*s" COLOR_BOLDGRAY "%*c" COLOR_OFF "\n",
caption_pos, caption,
LINE_LEN - 2 - caption_pos,
display_refresh.cmd ? get_flasher() : ' ');
display_text(text);
l_free(text);
text = l_strdup_printf("%s%s%s\n", COLOR_GRAY, dashed_line, COLOR_OFF);
display_text(text);
l_free(text);
va_start(args, fmt);
text = l_strdup_vprintf(fmt, args);
va_end(args);
body = l_strdup_printf("%s%s%s\n", COLOR_BOLDGRAY, text, COLOR_OFF);
display_text(body);
l_free(body);
l_free(text);
text = l_strdup_printf("%s%s%s\n", COLOR_GRAY, dashed_line, COLOR_OFF);
display_text(text);
l_free(text);
}
void display_table_footer(void)
{
display_text("\n");
if (display_refresh.cmd)
display_refresh_redo_lines();
}
void display_command_line(const char *command_family,
const struct command *cmd)
{
char *cmd_line = l_strdup_printf("%s%s%s%s%s %s",
command_family ? : "",
command_family ? " " : "",
cmd->entity ? : "",
cmd->entity ? " " : "",
cmd->cmd,
cmd->arg ? : "",
cmd->arg ? " " : "");
display(MARGIN "%-*s%s\n", 50, cmd_line, cmd->desc ? : "");
l_free(cmd_line);
}
static void display_completion_matches(char **matches, int num_matches,
int max_length)
{
char *prompt;
char *entry;
char line[LINE_LEN];
size_t index;
size_t line_used;
char *input = rl_copy_text(0, rl_end);
prompt = l_strdup_printf("%s%s\n", IWD_PROMPT, input);
l_free(input);
display_text(prompt);
l_free(prompt);
for (index = 1, line_used = 0; matches[index]; index++) {
if ((line_used + max_length) > LINE_LEN) {
strcpy(&line[line_used], "\n");
display_text(line);
line_used = 0;
}
entry = l_strdup_printf("%-*s ", max_length, matches[index]);
strcpy(&line[line_used], entry);
l_free(entry);
line_used += max_length + 1;
}
strcpy(&line[line_used], "\n");
display_text(line);
}
#define MAX_PASSPHRASE_LEN 63
static struct masked_input {
bool use_mask;
char passphrase[MAX_PASSPHRASE_LEN];
char mask[MAX_PASSPHRASE_LEN];
} masked_input;
static void mask_input(void)
{
int point;
char *line;
size_t len;
if (!masked_input.use_mask)
return;
line = rl_copy_text(0, rl_end);
len = strlen(line);
if (!len)
goto done;
point = rl_point;
if (len > MAX_PASSPHRASE_LEN) {
point--;
} else if (strlen(masked_input.passphrase) > len) {
masked_input.passphrase[len] = 0;
masked_input.mask[len] = 0;
} else {
masked_input.passphrase[len - 1] = line[len - 1];
masked_input.mask[len - 1] = '*';
}
rl_replace_line("", 0);
rl_redisplay();
rl_replace_line(masked_input.mask, 0);
rl_point = point;
rl_redisplay();
done:
l_free(line);
}
static void reset_masked_input(void)
{
memset(masked_input.passphrase, 0, MAX_PASSPHRASE_LEN);
memset(masked_input.mask, 0, MAX_PASSPHRASE_LEN);
}
static void readline_callback(char *prompt)
{
char **argv;
int argc;
HIST_ENTRY *previous_prompt;
if (!prompt) {
display_quit();
l_main_quit();
return;
}
if (agent_prompt(masked_input.use_mask ?
masked_input.passphrase : prompt))
goto done;
if (!strlen(prompt))
goto done;
previous_prompt = current_history();
if (!previous_prompt ||
(previous_prompt &&
strcmp(previous_prompt->line, prompt))) {
add_history(prompt);
}
argv = l_parse_args(prompt, &argc);
if (!argv) {
display("Invalid command\n");
goto done;
}
command_process_prompt(argv, argc);
l_strfreev(argv);
done:
l_free(prompt);
}
bool display_agent_is_active(void)
{
if (agent_saved_input)
return true;
return false;
}
static bool read_handler(struct l_io *io, void *user_data)
{
rl_callback_read_char();
if (display_agent_is_active())
mask_input();
return true;
}
static void disconnect_callback(struct l_io *io, void *user_data)
{
l_main_quit();
}
void display_enable_cmd_prompt(void)
{
if (!io)
io = l_io_new(fileno(stdin));
l_io_set_read_handler(io, read_handler, NULL, NULL);
l_io_set_disconnect_handler(io, disconnect_callback, NULL, NULL);
rl_set_prompt(IWD_PROMPT);
display("");
}
void display_disable_cmd_prompt(void)
{
display_refresh_reset();
rl_set_prompt("Waiting to connect to IWD");
printf("\r");
rl_on_new_line();
rl_redisplay();
}
void display_agent_prompt(const char *label, bool mask_input)
{
char *prompt;
if (agent_saved_input)
return;
masked_input.use_mask = mask_input;
if (mask_input)
reset_masked_input();
agent_saved_input = l_new(struct saved_input, 1);
agent_saved_input->point = rl_point;
agent_saved_input->line = rl_copy_text(0, rl_end);
rl_set_prompt("");
rl_replace_line("", 0);
rl_redisplay();
rl_erase_empty_line = 0;
prompt = l_strdup_printf(COLOR_BLUE "%s " COLOR_OFF, label);
rl_set_prompt(prompt);
l_free(prompt);
rl_forced_update_display();
}
void display_agent_prompt_release(const char *label)
{
if (!agent_saved_input)
return;
if (display_refresh.cmd) {
char *text = rl_copy_text(0, rl_end);
char *prompt = l_strdup_printf(COLOR_BLUE "%s " COLOR_OFF
"%s\n", label, text);
l_free(text);
l_queue_push_tail(display_refresh.redo_entries, prompt);
display_refresh.undo_lines++;
}
rl_erase_empty_line = 1;
rl_replace_line(agent_saved_input->line, 0);
rl_point = agent_saved_input->point;
l_free(agent_saved_input->line);
l_free(agent_saved_input);
agent_saved_input = NULL;
rl_set_prompt(IWD_PROMPT);
rl_forced_update_display();
display("\r");
}
void display_quit(void)
{
rl_insert_text("quit");
rl_redisplay();
rl_crlf();
}
static void signal_handler(struct l_signal *signal, uint32_t signo,
void *user_data)
{
switch (signo) {
case SIGWINCH:
if (display_refresh.cmd)
display_refresh_reset();
break;
}
}
void display_init(void)
{
sigset_t mask;
memset(&dashed_line, '-', sizeof(dashed_line) - 1);
memset(&empty_line, ' ', sizeof(empty_line) - 1);
display_refresh.redo_entries = l_queue_new();
setlinebuf(stdout);
sigemptyset(&mask);
sigaddset(&mask, SIGWINCH);
resize_signal = l_signal_create(&mask, signal_handler, NULL, NULL);
rl_attempted_completion_function = command_completion;
rl_completion_display_matches_hook = display_completion_matches;
rl_erase_empty_line = 1;
rl_callback_handler_install("Waiting for IWD to appear...",
readline_callback);
rl_redisplay();
}
void display_exit(void)
{
if (agent_saved_input) {
l_free(agent_saved_input->line);
l_free(agent_saved_input);
agent_saved_input = NULL;
}
l_timeout_remove(refresh_timeout);
refresh_timeout = NULL;
l_queue_destroy(display_refresh.redo_entries, l_free);
rl_callback_handler_remove();
l_io_destroy(io);
l_signal_remove(resize_signal);
display_quit();
}