Use editline instead of GNU Readline
authorAdrian Iain Lam <adrianiainlam@users.noreply.github.com>
Mon, 19 Oct 2020 00:42:23 +0000 (01:42 +0100)
committerAdrian Iain Lam <adrianiainlam@users.noreply.github.com>
Mon, 19 Oct 2020 00:42:23 +0000 (01:42 +0100)
GNU Readline is GPL and so I can't link to it from this MIT-license project.

Including editline as a submodule.

.gitmodules
CMakeLists.txt
README.md
lib/editline [new submodule]
src/mouse_cursor_tracker.cpp

index e69de29..610a55f 100644 (file)
@@ -0,0 +1,3 @@
+[submodule "lib/editline"]
+       path = lib/editline
+       url = https://github.com/troglobit/editline.git
index 3a2ccf1..0d95009 100644 (file)
@@ -2,11 +2,20 @@ cmake_minimum_required(VERSION 3.16)
 
 project(MouseTrackerForCubism_project)
 
-find_library(xdo_LIBS NAMES xdo libxdo PATHS /usr/lib REQUIRED)
+find_library(xdo_LIBS NAMES xdo PATHS /usr/lib REQUIRED)
 find_library(pulse_LIBS NAMES pulse PATHS /usr/lib REQUIRED)
-find_library(readline_LIBS NAMES readline libreadline /usr/lib REQUIRED)
 
-include_directories(include)
+include(ExternalProject)
+ExternalProject_Add(editline
+    SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/editline/src
+    BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib/editline
+    CONFIGURE_COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lib/editline/autogen.sh COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lib/editline/configure --prefix=<INSTALL_DIR>
+    BUILD_COMMAND ${MAKE})
+
+ExternalProject_Get_Property(editline install_dir)
+set(editline_INSTALL_DIR ${install_dir})
+
+include_directories(include ${CMAKE_CURRENT_SOURCE_DIR}/lib/editline/include)
 
 add_library(
     MouseTrackerForCubism STATIC
@@ -18,4 +27,4 @@ set_target_properties(
     include/mouse_cursor_tracker.h
 )
 
-target_link_libraries(MouseTrackerForCubism ${xdo_LIBS} ${pulse_LIBS} pulse-simple ${readline_LIBS})
+target_link_libraries(MouseTrackerForCubism ${xdo_LIBS} ${pulse_LIBS} pulse-simple ${editline_INSTALL_DIR}/lib/libeditline.a)
index 8584238..16a8c46 100644 (file)
--- a/README.md
+++ b/README.md
@@ -31,16 +31,16 @@ if you don't have C++17 support.
 1. Install dependencies.
 
    You will require a recent C/C++ compiler, `make`, `patch`, CMake >= 3.16,
-   libxdo, PulseAudio, and readline. To compile the example
+   libxdo, and PulseAudio. To compile the example
    program you will also require the OpenGL library (and its dev headers)
    among other libraries required for the example program. The libraries I
    had to install (this list may not be exhaustive) are:
 
-       libxdo-dev libpulse-dev libgl1-mesa-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libglu1-mesa-dev libreadline-dev
+       libxdo-dev libpulse-dev libgl1-mesa-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libglu1-mesa-dev
 
-2. Clone this repository
+2. Clone this repository including its submodule (editline)
 
-       git clone https://github.com/adrianiainlam/mouse-tracker-for-cubism.git
+       git clone --recurse-submodules https://github.com/adrianiainlam/mouse-tracker-for-cubism.git
 
 3. To build the library only: (Skip this step if you want to build the example
    program. It will be done automatically.)
@@ -69,6 +69,17 @@ To build the example program:
 
        ./build.sh
 
+   Note: I have observed that sometimes, on the first build, a failure
+   will be reported, such as:
+
+       [ 98%] Completed 'editline'
+       [ 98%] Built target editline
+       Makefile:149: recipe for target 'all' failed
+       make: *** [all] Error 2
+
+   I have no idea why, but seems like running build.sh again solves the
+   issue. Please open an issue / contact me if not.
+
 7. Now try running the example program. From the "example" directory:
 
        cd ./demo_build/build/make_gcc/bin/Demo/
@@ -119,6 +130,18 @@ I refer to the following files that I have provided under this repo:
 The license text can be found in LICENSE-MIT.txt, and also at the top of
 the .cpp and .h files.
 
+This library depends on "editline" which is included here as a git
+submodule. The fork used here was originally written by Simmule
+Turner and Rick Salz, made available under the Spencer License 94,
+now maintained by Joachim Wiberg. See "lib/editline/LICENSE" for more
+information.
+
+(Note: An earlier version of this library linked to GNU Readline as
+a dependency. I have made a mistake and assumed GNU Readline was LGPL,
+when in fact it was GPL. This has been corrected within 24 hours, by
+replacing GNU Readline with editline instead. I apologize to the
+copyright holders of GNU Readline.)
+
 The example program is a patched version of the sample program provided
 by Live2D (because there's really no point in reinventing the wheel),
 and as such, as per the licensing restrictions by Live2D, is still the
diff --git a/lib/editline b/lib/editline
new file mode 160000 (submodule)
index 0000000..62bba78
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 62bba782585c6c18d7a2b27beeb60a45db9abb43
index 9892482..11defe5 100644 (file)
@@ -41,8 +41,8 @@ extern "C"
 {
 #include <xdo.h>
 #include <pulse/simple.h>
-#include <readline/readline.h>
-#include <readline/history.h>
+#include <string.h> // strdup
+#include "editline.h"
 }
 #include "mouse_cursor_tracker.h"
 
@@ -146,10 +146,9 @@ std::vector<std::pair<std::string, int> > MCT_motions;
 std::vector<std::string> MCT_expressions;
 std::map<std::string, double> *MCT_overrideMap;
 
-static char **cliCompletionFunction(const char *textCStr, int start, int end)
+static int cliListPossible(char *token, char ***av)
 {
     // Reference: https://github.com/eliben/code-for-blog/blob/master/2016/readline-samples/readline-complete-subcommand.cpp
-    rl_attempted_completion_over = 1;
 
     std::string line(rl_line_buffer);
 
@@ -193,6 +192,7 @@ static char **cliCompletionFunction(const char *textCStr, int start, int end)
                 {
                     constructed.push_back(std::to_string(i));
                 }
+                break; // No need to loop through the list, all motions have the same set of priorities
             }
         }
 
@@ -222,10 +222,10 @@ static char **cliCompletionFunction(const char *textCStr, int start, int end)
 
     if (!vocab)
     {
-        return nullptr;
+        return 0;
     }
 
-    std::string text(textCStr);
+    std::string text(token);
     std::vector<std::string> matches;
     std::copy_if(vocab->begin(), vocab->end(), std::back_inserter(matches),
                  [&text](const std::string &s)
@@ -234,20 +234,48 @@ static char **cliCompletionFunction(const char *textCStr, int start, int end)
                              s.compare(0, text.size(), text) == 0);
                  });
 
-    if (matches.empty())
+    *av = static_cast<char **>(malloc(matches.size() * sizeof (**av)));
+    if (!*av) return 0;
+    for (std::size_t i = 0; i < matches.size(); i++)
     {
-        return nullptr;
+        (*av)[i] = strdup(matches[i].c_str());
     }
+    return matches.size();
+}
+
+static char *cliComplete(char *token, int *match)
+{
+    char **av;
+    int numMatches = cliListPossible(token, &av);
+
+    *match = 0;
+    if (numMatches == 0) return nullptr;
 
-    char** array =
-        static_cast<char**>(malloc((2 + matches.size()) * sizeof(*array)));
-    array[0] = strdup(longest_common_prefix(text, matches).c_str());
-    size_t ptr = 1;
-    for (const auto& m : matches) {
-        array[ptr++] = strdup(m.c_str());
+    std::vector<std::string> list;
+
+    for (std::size_t i = 0; i < numMatches; i++)
+    {
+        list.push_back(std::string(av[i]));
+        free(av[i]);
     }
-    array[ptr] = nullptr;
-    return array;
+    free(av);
+
+    if (numMatches == 1)
+    {
+        *match = 1;
+        std::string result = list[0] + " ";
+        return strdup(result.c_str() + strlen(token));
+    }
+
+    std::string lcp = longest_common_prefix(token, list);
+
+    if (lcp.size() > strlen(token))
+    {
+        *match = 1;
+        return strdup(lcp.c_str() + strlen(token));
+    }
+
+    return nullptr;
 }
 
 MouseCursorTracker::MouseCursorTracker(std::string cfgPath,
@@ -303,8 +331,8 @@ void MouseCursorTracker::audioLoop(void)
 
 void MouseCursorTracker::cliLoop(void)
 {
-    rl_catch_signals = 0;
-    rl_attempted_completion_function = cliCompletionFunction;
+    rl_set_complete_func(&cliComplete);
+    rl_set_list_possib_func(&cliListPossible);
     while (!m_stop)
     {
         char *buf = readline(">> ");
@@ -430,8 +458,6 @@ void MouseCursorTracker::processCommand(std::string cmdline)
                 {
                     std::cerr << "std::stod failed" << std::endl;
                 }
-
-                std::cerr << "Debug: setting " << cmdSplit[i] << std::endl;
             }
         }
         else if (cmdSplit[0] == "clear")