Merge pull request #2074

e1f3dfcc Add readline support to cli (jethro)
This commit is contained in:
Riccardo Spagni 2017-06-18 17:26:08 +02:00
commit ae5f7c71d7
No known key found for this signature in database
GPG Key ID: 55432DF31CCD4FCD
8 changed files with 376 additions and 1 deletions

View File

@ -655,6 +655,19 @@ endif()
list(APPEND EXTRA_LIBRARIES ${CMAKE_DL_LIBS}) list(APPEND EXTRA_LIBRARIES ${CMAKE_DL_LIBS})
option(USE_READLINE "Build with GNU readline support." ON)
if(USE_READLINE)
find_package(Readline)
if(READLINE_FOUND AND GNU_READLINE_FOUND)
add_definitions(-DHAVE_READLINE)
include_directories(${Readline_INCLUDE_DIR})
list(APPEND EXTRA_LIBRARIES ${Readline_LIBRARY})
message(STATUS "Found readline library at: ${Readline_ROOT_DIR}")
else()
message(STATUS "Could not find GNU readline library so building without readline support")
endif()
endif()
if(ANDROID) if(ANDROID)
set(ATOMIC libatomic.a) set(ATOMIC libatomic.a)
endif() endif()

66
cmake/FindReadline.cmake Normal file
View File

@ -0,0 +1,66 @@
# - Try to find readline include dirs and libraries
#
# Usage of this module as follows:
#
# find_package(Readline)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# Readline_ROOT_DIR Set this variable to the root installation of
# readline if the module has problems finding the
# proper installation path.
#
# Variables defined by this module:
#
# READLINE_FOUND System has readline, include and lib dirs found
# GNU_READLINE_FOUND Version of readline found is GNU readline, not libedit!
# Readline_INCLUDE_DIR The readline include directories.
# Readline_LIBRARY The readline library.
find_path(Readline_ROOT_DIR
NAMES include/readline/readline.h
PATHS /opt/local/ /usr/local/ /usr/
NO_DEFAULT_PATH
)
find_path(Readline_INCLUDE_DIR
NAMES readline/readline.h
PATHS ${Readline_ROOT_DIR}/include
NO_DEFAULT_PATH
)
find_library(Readline_LIBRARY
NAMES readline
PATHS ${Readline_ROOT_DIR}/lib
NO_DEFAULT_PATH
)
if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
set(READLINE_FOUND TRUE)
else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
FIND_LIBRARY(Readline_LIBRARY NAMES readline PATHS Readline_ROOT_DIR)
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY )
MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY)
endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
mark_as_advanced(
Readline_ROOT_DIR
Readline_INCLUDE_DIR
Readline_LIBRARY
)
set(CMAKE_REQUIRED_INCLUDES ${Readline_INCLUDE_DIR})
set(CMAKE_REQUIRED_LIBRARIES ${Readline_LIBRARY})
INCLUDE(CheckCXXSourceCompiles)
CHECK_CXX_SOURCE_COMPILES(
"
#include <stdio.h>
#include <readline/readline.h>
int
main()
{
char * s = rl_copy_text(0, 0);
}
" GNU_READLINE_FOUND)

View File

@ -38,6 +38,10 @@
#endif #endif
#include <boost/thread.hpp> #include <boost/thread.hpp>
#ifdef HAVE_READLINE
#include "readline_buffer.h"
#endif
namespace epee namespace epee
{ {
class async_stdin_reader class async_stdin_reader
@ -49,6 +53,10 @@ namespace epee
, m_read_status(state_init) , m_read_status(state_init)
{ {
m_reader_thread = boost::thread(std::bind(&async_stdin_reader::reader_thread_func, this)); m_reader_thread = boost::thread(std::bind(&async_stdin_reader::reader_thread_func, this));
#ifdef HAVE_READLINE
m_readline_buffer.start();
m_readline_thread = boost::thread(std::bind(&async_stdin_reader::readline_thread_func, this));
#endif
} }
~async_stdin_reader() ~async_stdin_reader()
@ -56,6 +64,13 @@ namespace epee
stop(); stop();
} }
#ifdef HAVE_READLINE
rdln::readline_buffer& get_readline_buffer()
{
return m_readline_buffer;
}
#endif
// Not thread safe. Only one thread can call this method at once. // Not thread safe. Only one thread can call this method at once.
bool get_line(std::string& line) bool get_line(std::string& line)
{ {
@ -98,6 +113,10 @@ namespace epee
m_request_cv.notify_one(); m_request_cv.notify_one();
m_reader_thread.join(); m_reader_thread.join();
#ifdef HAVE_READLINE
m_readline_buffer.stop();
m_readline_thread.join();
#endif
} }
} }
@ -174,6 +193,16 @@ namespace epee
return true; return true;
} }
#ifdef HAVE_READLINE
void readline_thread_func()
{
while (m_run.load(std::memory_order_relaxed))
{
m_readline_buffer.process();
}
}
#endif
void reader_thread_func() void reader_thread_func()
{ {
while (true) while (true)
@ -187,7 +216,11 @@ namespace epee
{ {
if (m_run.load(std::memory_order_relaxed)) if (m_run.load(std::memory_order_relaxed))
{ {
#ifdef HAVE_READLINE
m_readline_buffer.get_line(line);
#else
std::getline(std::cin, line); std::getline(std::cin, line);
#endif
read_ok = !std::cin.eof() && !std::cin.fail(); read_ok = !std::cin.eof() && !std::cin.fail();
} }
} }
@ -229,6 +262,10 @@ namespace epee
private: private:
boost::thread m_reader_thread; boost::thread m_reader_thread;
std::atomic<bool> m_run; std::atomic<bool> m_run;
#ifdef HAVE_READLINE
boost::thread m_readline_thread;
rdln::readline_buffer m_readline_buffer;
#endif
std::string m_line; std::string m_line;
bool m_has_read_request; bool m_has_read_request;
@ -277,12 +314,16 @@ namespace epee
{ {
if (!m_prompt.empty()) if (!m_prompt.empty())
{ {
#ifdef HAVE_READLINE
m_stdin_reader.get_readline_buffer().set_prompt(m_prompt);
#else
epee::set_console_color(epee::console_color_yellow, true); epee::set_console_color(epee::console_color_yellow, true);
std::cout << m_prompt; std::cout << m_prompt;
if (' ' != m_prompt.back()) if (' ' != m_prompt.back())
std::cout << ' '; std::cout << ' ';
epee::reset_console_color(); epee::reset_console_color();
std::cout.flush(); std::cout.flush();
#endif
} }
} }

View File

@ -0,0 +1,40 @@
#pragma once
#include <streambuf>
#include <sstream>
#include <iostream>
namespace rdln
{
class readline_buffer : public std::stringbuf
{
public:
readline_buffer();
void start();
void stop();
int process();
bool is_running()
{
return m_cout_buf != NULL;
}
void get_line(std::string& line);
void set_prompt(const std::string& prompt);
protected:
virtual int sync();
private:
std::streambuf* m_cout_buf;
};
class suspend_readline
{
public:
suspend_readline();
~suspend_readline();
private:
readline_buffer* m_buffer;
bool m_restart;
};
}

View File

@ -26,7 +26,12 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (USE_READLINE AND GNU_READLINE_FOUND)
add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp readline_buffer.cpp)
else()
add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp) add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp)
endif()
# Build and install libepee if we're building for GUI # Build and install libepee if we're building for GUI
if (BUILD_GUI_DEPS) if (BUILD_GUI_DEPS)
if(IOS) if(IOS)

View File

@ -0,0 +1,196 @@
#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();
}

View File

@ -37,6 +37,10 @@
#include "cryptonote_config.h" #include "cryptonote_config.h"
#include "string_tools.h" #include "string_tools.h"
#ifdef HAVE_READLINE
#include "readline_buffer.h"
#endif
namespace command_line namespace command_line
{ {
namespace namespace
@ -49,6 +53,9 @@ namespace command_line
std::string input_line(const std::string& prompt) std::string input_line(const std::string& prompt)
{ {
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
#endif
std::cout << prompt; std::cout << prompt;
std::string buf; std::string buf;

View File

@ -42,6 +42,10 @@
#include <unistd.h> #include <unistd.h>
#endif #endif
#ifdef HAVE_READLINE
#include "readline_buffer.h"
#endif
namespace namespace
{ {
#if defined(_WIN32) #if defined(_WIN32)
@ -238,6 +242,9 @@ namespace tools
boost::optional<password_container> password_container::prompt(const bool verify, const char *message) boost::optional<password_container> password_container::prompt(const bool verify, const char *message)
{ {
#ifdef HAVE_READLINE
rdln::suspend_readline pause_readline;
#endif
password_container pass1{}; password_container pass1{};
password_container pass2{}; password_container pass2{};
if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password))