aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2017-06-18 17:26:08 +0200
committerRiccardo Spagni <ric@spagni.net>2017-06-18 17:26:08 +0200
commitae5f7c71d790a48ca696644997a791ee12973423 (patch)
treeab17a0843d82f9ee641fa06b501e2e1f2db2e0d9
parentMerge pull request #2070 (diff)
parentAdd readline support to cli (diff)
downloadmonero-ae5f7c71d790a48ca696644997a791ee12973423.tar.xz
Merge pull request #2074
e1f3dfcc Add readline support to cli (jethro)
-rw-r--r--CMakeLists.txt13
-rw-r--r--cmake/FindReadline.cmake66
-rw-r--r--contrib/epee/include/console_handler.h41
-rw-r--r--contrib/epee/include/readline_buffer.h40
-rw-r--r--contrib/epee/src/CMakeLists.txt7
-rw-r--r--contrib/epee/src/readline_buffer.cpp196
-rw-r--r--src/common/command_line.cpp7
-rw-r--r--src/common/password.cpp7
8 files changed, 376 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a9d6be986..a8060f90c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -655,6 +655,19 @@ endif()
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)
set(ATOMIC libatomic.a)
endif()
diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake
new file mode 100644
index 000000000..9ccef7ad8
--- /dev/null
+++ b/cmake/FindReadline.cmake
@@ -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)
diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h
index 54e3e966d..bb20faa65 100644
--- a/contrib/epee/include/console_handler.h
+++ b/contrib/epee/include/console_handler.h
@@ -38,6 +38,10 @@
#endif
#include <boost/thread.hpp>
+#ifdef HAVE_READLINE
+ #include "readline_buffer.h"
+#endif
+
namespace epee
{
class async_stdin_reader
@@ -49,6 +53,10 @@ namespace epee
, m_read_status(state_init)
{
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()
@@ -56,6 +64,13 @@ namespace epee
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.
bool get_line(std::string& line)
{
@@ -98,6 +113,10 @@ namespace epee
m_request_cv.notify_one();
m_reader_thread.join();
+#ifdef HAVE_READLINE
+ m_readline_buffer.stop();
+ m_readline_thread.join();
+#endif
}
}
@@ -174,6 +193,16 @@ namespace epee
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()
{
while (true)
@@ -187,7 +216,11 @@ namespace epee
{
if (m_run.load(std::memory_order_relaxed))
{
+#ifdef HAVE_READLINE
+ m_readline_buffer.get_line(line);
+#else
std::getline(std::cin, line);
+#endif
read_ok = !std::cin.eof() && !std::cin.fail();
}
}
@@ -229,6 +262,10 @@ namespace epee
private:
boost::thread m_reader_thread;
std::atomic<bool> m_run;
+#ifdef HAVE_READLINE
+ boost::thread m_readline_thread;
+ rdln::readline_buffer m_readline_buffer;
+#endif
std::string m_line;
bool m_has_read_request;
@@ -277,12 +314,16 @@ namespace epee
{
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);
std::cout << m_prompt;
if (' ' != m_prompt.back())
std::cout << ' ';
epee::reset_console_color();
std::cout.flush();
+#endif
}
}
diff --git a/contrib/epee/include/readline_buffer.h b/contrib/epee/include/readline_buffer.h
new file mode 100644
index 000000000..7d929bc4c
--- /dev/null
+++ b/contrib/epee/include/readline_buffer.h
@@ -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;
+ };
+}
+
diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt
index 437851833..c61a6e684 100644
--- a/contrib/epee/src/CMakeLists.txt
+++ b/contrib/epee/src/CMakeLists.txt
@@ -26,7 +26,12 @@
# 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.
-add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp)
+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)
+endif()
+
# Build and install libepee if we're building for GUI
if (BUILD_GUI_DEPS)
if(IOS)
diff --git a/contrib/epee/src/readline_buffer.cpp b/contrib/epee/src/readline_buffer.cpp
new file mode 100644
index 000000000..68b739db9
--- /dev/null
+++ b/contrib/epee/src/readline_buffer.cpp
@@ -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();
+}
+
diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp
index f71b3e576..8c2796bbe 100644
--- a/src/common/command_line.cpp
+++ b/src/common/command_line.cpp
@@ -37,6 +37,10 @@
#include "cryptonote_config.h"
#include "string_tools.h"
+#ifdef HAVE_READLINE
+ #include "readline_buffer.h"
+#endif
+
namespace command_line
{
namespace
@@ -49,6 +53,9 @@ namespace command_line
std::string input_line(const std::string& prompt)
{
+#ifdef HAVE_READLINE
+ rdln::suspend_readline pause_readline;
+#endif
std::cout << prompt;
std::string buf;
diff --git a/src/common/password.cpp b/src/common/password.cpp
index bdc9c69c0..5c04023f4 100644
--- a/src/common/password.cpp
+++ b/src/common/password.cpp
@@ -42,6 +42,10 @@
#include <unistd.h>
#endif
+#ifdef HAVE_READLINE
+ #include "readline_buffer.h"
+#endif
+
namespace
{
#if defined(_WIN32)
@@ -238,6 +242,9 @@ namespace tools
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 pass2{};
if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password))