mirror of
https://github.com/monero-project/monero.git
synced 2024-12-27 05:59:28 +00:00
e1f3dfccc8
This PR adds readline support to the daemon and monero-wallet-cli. Only GNU readline is supported (e.g. not libedit) and there are cmake checks to ensure this. There is a cmake variable, Readline_ROOT_DIR that can specify a directory to find readline, otherwise some default paths are searched. There is also a cmake option, USE_READLINE, that defaults to ON. If set to ON, if readline is not found, the build continues but without readline support. One negative side effect of using readline is that the color prompt in the wallet-cli now has no color and just uses terminal default. I know how to fix this but it's quite a big change so will tackle another time.
196 lines
3.4 KiB
C++
196 lines
3.4 KiB
C++
#include "readline_buffer.h"
|
|
#include <readline/readline.h>
|
|
#include <readline/history.h>
|
|
#include <sys/select.h>
|
|
#include <unistd.h>
|
|
#include <mutex>
|
|
#include <condition_variable>
|
|
|
|
static int process_input();
|
|
static void install_line_handler();
|
|
static void remove_line_handler();
|
|
|
|
static std::string last_line;
|
|
static std::string last_prompt;
|
|
std::mutex line_mutex, sync_mutex;
|
|
std::condition_variable have_line;
|
|
|
|
namespace
|
|
{
|
|
rdln::readline_buffer* current = NULL;
|
|
}
|
|
|
|
rdln::suspend_readline::suspend_readline()
|
|
{
|
|
m_buffer = current;
|
|
if(!m_buffer)
|
|
return;
|
|
m_restart = m_buffer->is_running();
|
|
if(m_restart)
|
|
m_buffer->stop();
|
|
}
|
|
|
|
rdln::suspend_readline::~suspend_readline()
|
|
{
|
|
if(!m_buffer)
|
|
return;
|
|
if(m_restart)
|
|
m_buffer->start();
|
|
}
|
|
|
|
rdln::readline_buffer::readline_buffer()
|
|
: std::stringbuf()
|
|
{
|
|
current = this;
|
|
}
|
|
|
|
void rdln::readline_buffer::start()
|
|
{
|
|
if(m_cout_buf != NULL)
|
|
return;
|
|
m_cout_buf = std::cout.rdbuf();
|
|
std::cout.rdbuf(this);
|
|
install_line_handler();
|
|
}
|
|
|
|
void rdln::readline_buffer::stop()
|
|
{
|
|
if(m_cout_buf == NULL)
|
|
return;
|
|
std::cout.rdbuf(m_cout_buf);
|
|
m_cout_buf = NULL;
|
|
remove_line_handler();
|
|
}
|
|
|
|
void rdln::readline_buffer::get_line(std::string& line)
|
|
{
|
|
std::unique_lock<std::mutex> lock(line_mutex);
|
|
have_line.wait(lock);
|
|
line = last_line;
|
|
}
|
|
|
|
void rdln::readline_buffer::set_prompt(const std::string& prompt)
|
|
{
|
|
last_prompt = prompt;
|
|
if(m_cout_buf == NULL)
|
|
return;
|
|
rl_set_prompt(last_prompt.c_str());
|
|
rl_redisplay();
|
|
}
|
|
|
|
int rdln::readline_buffer::process()
|
|
{
|
|
if(m_cout_buf == NULL)
|
|
return 0;
|
|
return process_input();
|
|
}
|
|
|
|
int rdln::readline_buffer::sync()
|
|
{
|
|
std::lock_guard<std::mutex> lock(sync_mutex);
|
|
char* saved_line;
|
|
int saved_point;
|
|
|
|
saved_point = rl_point;
|
|
saved_line = rl_copy_text(0, rl_end);
|
|
|
|
rl_set_prompt("");
|
|
rl_replace_line("", 0);
|
|
rl_redisplay();
|
|
|
|
do
|
|
{
|
|
char x = this->sgetc();
|
|
m_cout_buf->sputc(x);
|
|
}
|
|
while ( this->snextc() != EOF );
|
|
|
|
rl_set_prompt(last_prompt.c_str());
|
|
rl_replace_line(saved_line, 0);
|
|
rl_point = saved_point;
|
|
rl_redisplay();
|
|
free(saved_line);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static fd_set fds;
|
|
|
|
static int process_input()
|
|
{
|
|
int count;
|
|
struct timeval t;
|
|
|
|
t.tv_sec = 0;
|
|
t.tv_usec = 0;
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(STDIN_FILENO, &fds);
|
|
count = select(FD_SETSIZE, &fds, NULL, NULL, &t);
|
|
if (count < 1)
|
|
{
|
|
return count;
|
|
}
|
|
rl_callback_read_char();
|
|
return count;
|
|
}
|
|
|
|
static void handle_line(char* line)
|
|
{
|
|
if (line != NULL)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sync_mutex);
|
|
rl_set_prompt(last_prompt.c_str());
|
|
rl_already_prompted = 1;
|
|
return;
|
|
}
|
|
rl_set_prompt("");
|
|
rl_replace_line("", 0);
|
|
rl_redisplay();
|
|
rl_set_prompt(last_prompt.c_str());
|
|
}
|
|
|
|
static int handle_enter(int x, int y)
|
|
{
|
|
std::lock_guard<std::mutex> lock(sync_mutex);
|
|
char* line = NULL;
|
|
|
|
line = rl_copy_text(0, rl_end);
|
|
rl_set_prompt("");
|
|
rl_replace_line("", 1);
|
|
rl_redisplay();
|
|
|
|
if (strcmp(line, "") != 0)
|
|
{
|
|
last_line = line;
|
|
add_history(line);
|
|
have_line.notify_one();
|
|
}
|
|
free(line);
|
|
|
|
rl_set_prompt(last_prompt.c_str());
|
|
rl_redisplay();
|
|
|
|
rl_done = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int startup_hook()
|
|
{
|
|
rl_bind_key(RETURN, handle_enter);
|
|
rl_bind_key(NEWLINE, handle_enter);
|
|
return 0;
|
|
}
|
|
|
|
static void install_line_handler()
|
|
{
|
|
rl_startup_hook = startup_hook;
|
|
rl_callback_handler_install("", handle_line);
|
|
}
|
|
|
|
static void remove_line_handler()
|
|
{
|
|
rl_unbind_key(RETURN);
|
|
rl_callback_handler_remove();
|
|
}
|
|
|