diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1cf1d18..5d9c3b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,6 +66,7 @@ set(source_files juci.h directories.h directories.cc terminal.h + terminal.cc tooltips.h tooltips.cc singletons.h @@ -73,6 +74,8 @@ set(source_files juci.h cmake.h cmake.cc dialogs.cc + process.h + process.cc ../libclangmm/src/CodeCompleteResults.cc ../libclangmm/src/CompilationDatabase.cc @@ -90,10 +93,10 @@ set(source_files juci.h ../libclangmm/src/Utility.cc) if(MSYS) - list(APPEND source_files terminal_win.cc) + list(APPEND source_files process_win.cc) list(APPEND source_files dialogs_win.cc) else() - list(APPEND source_files terminal_unix.cc) + list(APPEND source_files process_unix.cc) list(APPEND source_files dialogs_unix.cc) endif() diff --git a/src/cmake.cc b/src/cmake.cc index 0501921..dae62fa 100644 --- a/src/cmake.cc +++ b/src/cmake.cc @@ -47,7 +47,7 @@ CMake::CMake(const boost::filesystem::path &path) { bool CMake::create_compile_commands(const boost::filesystem::path &path) { Dialog::Message message("Creating "+path.string()+"/compile_commands.json"); - auto exit_code=Singleton::terminal->execute(Singleton::config->terminal.cmake_command+" . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", path); + auto exit_code=Singleton::terminal->process(Singleton::config->terminal.cmake_command+" . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON", path); message.hide(); if(exit_code==EXIT_SUCCESS) { #ifdef _WIN32 //Temporary fix to MSYS2's libclang diff --git a/src/juci.cc b/src/juci.cc index 98125f1..73b2325 100644 --- a/src/juci.cc +++ b/src/juci.cc @@ -68,7 +68,7 @@ void Application::on_activate() { } std::thread another_juci_app([this, directory, files_in_directory](){ Singleton::terminal->async_print("Executing: juci "+directory.string()+files_in_directory); - Singleton::terminal->execute("juci "+directory.string()+files_in_directory, "", false); + Singleton::terminal->process("juci "+directory.string()+files_in_directory, "", false); }); another_juci_app.detach(); } diff --git a/src/process.cc b/src/process.cc new file mode 100644 index 0000000..7e20fe6 --- /dev/null +++ b/src/process.cc @@ -0,0 +1,21 @@ +#include "process.h" + +#include //TODO: remove +using namespace std; //TODO: remove + +Process::Process(const std::string &command, const std::string &path, + std::function read_stdout, + std::function read_stderr, + bool use_stdin, size_t buffer_size): + read_stdout(read_stdout), read_stderr(read_stderr), use_stdin(use_stdin), buffer_size(buffer_size) { + id=open(command, path); + if(id>0) + async_read(); +} + +Process::~Process() { + if(stdout_thread.joinable()) + stdout_thread.join(); + if(stderr_thread.joinable()) + stderr_thread.join(); +} diff --git a/src/process.h b/src/process.h new file mode 100644 index 0000000..3c8bfe2 --- /dev/null +++ b/src/process.h @@ -0,0 +1,52 @@ +#ifndef JUCI_PROCESS_H_ +#define JUCI_PROCESS_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#else + typedef pid_t process_id_type; + typedef int file_descriptor_type; + typedef int exit_code_type; +#endif + +class Process { +public: + Process(const std::string &command, const std::string &path=std::string(), + std::function read_stdout=nullptr, + std::function read_stderr=nullptr, + bool use_stdin=false, + size_t buffer_size=131072); + ~Process(); + + ///Get the process id of the started process. + process_id_type get_id() {return id;} + ///Wait until process is finished, and return exit_code. + exit_code_type get_exit_code(); + bool write(const char *bytes, size_t n); + + ///Kill a given process id. + static void kill(process_id_type id, bool force=false); + +private: + std::function read_stdout; + std::function read_stderr; + std::thread stdout_thread, stderr_thread; + bool use_stdin; + std::mutex stdin_mutex; + const size_t buffer_size; + + std::unique_ptr stdout_fd, stderr_fd, stdin_fd; + + process_id_type open(const std::string &command, const std::string &path); + process_id_type id; + void async_read(); +}; + +#endif // JUCI_PROCESS_H_ diff --git a/src/process_unix.cc b/src/process_unix.cc new file mode 100644 index 0000000..068a817 --- /dev/null +++ b/src/process_unix.cc @@ -0,0 +1,151 @@ +#include "process.h" +#include +#include +#include + +#include //TODO: remove +using namespace std; //TODO: remove + +pid_t Process::open(const std::string &command, const std::string &path) { + if(use_stdin) + stdin_fd=std::unique_ptr(new int); + if(read_stdout) + stdout_fd=std::unique_ptr(new int); + if(read_stderr) + stderr_fd=std::unique_ptr(new int); + + int stdin_p[2], stdout_p[2], stderr_p[2]; + + if(stdin_fd && pipe(stdin_p)!=0) { + close(stdin_p[0]); + close(stdin_p[1]); + return -1; + } + if(stdout_fd && pipe(stdout_p)!=0) { + if(stdin_fd) close(stdin_p[0]); + if(stdin_fd) close(stdin_p[1]); + close(stdout_p[0]); + close(stdout_p[1]); + return -1; + } + if(stderr_fd && pipe(stderr_p)!=0) { + if(stdin_fd) close(stdin_p[0]); + if(stdin_fd) close(stdin_p[1]); + if(stdout_fd) close(stdout_p[0]); + if(stdout_fd) close(stdout_p[1]); + close(stderr_p[0]); + close(stderr_p[1]); + return -1; + } + + pid_t pid = fork(); + + if (pid < 0) { + if(stdin_fd) close(stdin_p[0]); + if(stdin_fd) close(stdin_p[1]); + if(stdout_fd) close(stdout_p[0]); + if(stdout_fd) close(stdout_p[1]); + if(stderr_fd) close(stderr_p[0]); + if(stderr_fd) close(stderr_p[1]); + return pid; + } + else if (pid == 0) { + if(stdin_fd) close(stdin_p[1]); + if(stdout_fd) close(stdout_p[0]); + if(stderr_fd) close(stderr_p[0]); + if(stdin_fd) dup2(stdin_p[0], 0); + if(stdout_fd) dup2(stdout_p[1], 1); + if(stderr_fd) dup2(stderr_p[1], 2); + + setpgid(0, 0); + //TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe + //TODO: One solution is: echo "command;exit"|script -q /dev/null + + if(!path.empty()) + execl("/bin/sh", "sh", "-c", ("cd \""+path+"\" && "+command).c_str(), NULL); + else + execl("/bin/sh", "sh", "-c", command.c_str(), NULL); + + perror("execl"); + exit(EXIT_FAILURE); + } + + if(stdin_fd) close(stdin_p[0]); + if(stdout_fd) close(stdout_p[1]); + if(stderr_fd) close(stderr_p[1]); + + if(stdin_fd) *stdin_fd = stdin_p[1]; + if(stdout_fd) *stdout_fd = stdout_p[0]; + if(stderr_fd) *stderr_fd = stderr_p[0]; + + return pid; +} + +void Process::async_read() { + if(stdout_fd) { + stdout_thread=std::thread([this](){ + char buffer[buffer_size]; + ssize_t n; + while ((n=read(*stdout_fd, buffer, buffer_size)) > 0) + read_stdout(buffer, static_cast(n)); + }); + } + if(stderr_fd) { + stderr_thread=std::thread([this](){ + char buffer[buffer_size]; + ssize_t n; + while ((n=read(*stderr_fd, buffer, buffer_size)) > 0) + read_stderr(buffer, static_cast(n)); + }); + } +} + +int Process::get_exit_code() { + int exit_code; + waitpid(id, &exit_code, 0); + + if(stdout_thread.joinable()) + stdout_thread.join(); + if(stderr_thread.joinable()) + stderr_thread.join(); + + stdin_mutex.lock(); + if(stdin_fd) { + close(*stdin_fd); + stdin_fd.reset(); + } + stdin_mutex.unlock(); + if(stdout_fd) { + close(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + close(*stderr_fd); + stderr_fd.reset(); + } + + return exit_code; +} + +bool Process::write(const char *bytes, size_t n) { + stdin_mutex.lock(); + if(stdin_fd) { + if(::write(*stdin_fd, bytes, n)>=0) { + stdin_mutex.unlock(); + return true; + } + else { + stdin_mutex.unlock(); + return false; + } + } + stdin_mutex.unlock(); + return false; +} + +void Process::kill(process_id_type id, bool force) { + if(force) + ::kill(-id, SIGTERM); + else + ::kill(-id, SIGINT); +} diff --git a/src/process_win.cc b/src/process_win.cc new file mode 100644 index 0000000..b61c0db --- /dev/null +++ b/src/process_win.cc @@ -0,0 +1,3 @@ +#include "process.h" +#include +#include diff --git a/src/source_clang.cc b/src/source_clang.cc index f0c64b0..312715a 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -988,7 +988,7 @@ Source::ClangViewAutocomplete(file_path, project_path, language) { std::stringstream stdin_stream(get_buffer()->get_text()), stdout_stream; - auto exit_code=Singleton::terminal->execute(stdin_stream, stdout_stream, command); + auto exit_code=Singleton::terminal->process(stdin_stream, stdout_stream, command); if(exit_code==0) { get_source_buffer()->begin_user_action(); auto iter=get_buffer()->get_insert()->get_iter(); diff --git a/src/terminal.cc b/src/terminal.cc new file mode 100644 index 0000000..e9c8d42 --- /dev/null +++ b/src/terminal.cc @@ -0,0 +1,273 @@ +#include "terminal.h" +#include +#include "logging.h" +#include "singletons.h" +#include "process.h" + +#include //TODO: remove +using namespace std; //TODO: remove + +Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { + waiting_print.connect([this](){ + Singleton::terminal->async_print(line_nr-1, "."); + }); + start(start_msg); +} + +Terminal::InProgress::~InProgress() { + stop=true; + if(wait_thread.joinable()) + wait_thread.join(); +} + +void Terminal::InProgress::start(const std::string& msg) { + line_nr=Singleton::terminal->print(msg+"...\n"); + wait_thread=std::thread([this](){ + size_t c=0; + while(!stop) { + if(c%100==0) + waiting_print(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + c++; + } + }); +} + +void Terminal::InProgress::done(const std::string& msg) { + if(!stop) { + stop=true; + Singleton::terminal->async_print(line_nr-1, msg); + } +} + +void Terminal::InProgress::cancel(const std::string& msg) { + if(!stop) { + stop=true; + Singleton::terminal->async_print(line_nr-1, msg); + } +} + +Terminal::Terminal() { + bold_tag=get_buffer()->create_tag(); + bold_tag->property_weight()=PANGO_WEIGHT_BOLD; + + async_print_dispatcher.connect([this](){ + async_print_strings_mutex.lock(); + if(async_print_strings.size()>0) { + for(auto &string_bold: async_print_strings) + print(string_bold.first, string_bold.second); + async_print_strings.clear(); + } + async_print_strings_mutex.unlock(); + }); + async_print_on_line_dispatcher.connect([this](){ + async_print_on_line_strings_mutex.lock(); + if(async_print_on_line_strings.size()>0) { + for(auto &line_string: async_print_on_line_strings) + print(line_string.first, line_string.second); + async_print_on_line_strings.clear(); + } + async_print_on_line_strings_mutex.unlock(); + }); +} + +int Terminal::process(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { + std::unique_ptr process; + if(use_pipes) + process=std::unique_ptr(new Process(command, path.string(), [this](const char* bytes, size_t n) { + async_print(std::string(bytes, n)); + }, [this](const char* bytes, size_t n) { + async_print(std::string(bytes, n), true); + })); + else + process=std::unique_ptr(new Process(command, path.string())); + + if(process->get_id()<=0) { + async_print("Error: Failed to run command: " + command + "\n", true); + return -1; + } + + return process->get_exit_code(); +} + +int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path) { + Process process(command, path.string(), [this, &stdout_stream](const char* bytes, size_t n) { + Glib::ustring umessage(bytes, n); + Glib::ustring::iterator iter; + while(!umessage.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + umessage.replace(iter, next_char_iter, "?"); + } + stdout_stream.write(umessage.data(), n); + }, [this](const char* bytes, size_t n) { + async_print(std::string(bytes, n), true); + }); + + if(process.get_id()<=0) { + async_print("Error: Failed to run command: " + command + "\n", true); + return -1; + } + + char buffer[131072]; + for(;;) { + stdin_stream.readsome(buffer, 131072); + auto read_n=stdin_stream.gcount(); + if(read_n==0) + break; + if(!process.write(buffer, read_n)) + break; + } + + return process.get_exit_code(); +} + +void Terminal::async_process(const std::string &command, const boost::filesystem::path &path, std::function callback) { + std::thread async_execute_thread([this, command, path, callback](){ + processes_mutex.lock(); + stdin_buffer.clear(); + std::shared_ptr process(new Process(command, path.string(), [this](const char* bytes, size_t n) { + async_print(std::string(bytes, n)); + }, [this](const char* bytes, size_t n) { + async_print(std::string(bytes, n), true); + }, true)); + auto pid=process->get_id(); + if (pid<=0) { + processes_mutex.unlock(); + async_print("Error: Failed to run command: " + command + "\n", true); + if(callback) + callback(-1); + return; + } + else { + processes.emplace_back(process); + processes_mutex.unlock(); + } + + exit_code_type exit_code=process->get_exit_code(); + + processes_mutex.lock(); + for(auto it=processes.begin();it!=processes.end();it++) { + if((*it)->get_id()==pid) { + processes.erase(it); + break; + } + } + stdin_buffer.clear(); + processes_mutex.unlock(); + + if(callback) + callback(exit_code); + }); + async_execute_thread.detach(); +} + +void Terminal::kill_last_async_process(bool force) { + processes_mutex.lock(); + if(processes.size()>0) + Process::kill(processes.back()->get_id(), force); + processes_mutex.unlock(); +} + +void Terminal::kill_async_processes(bool force) { + processes_mutex.lock(); + for(auto &process: processes) + Process::kill(process->get_id(), force); + processes_mutex.unlock(); +} + +size_t Terminal::print(const std::string &message, bool bold){ + Glib::ustring umessage=message; + Glib::ustring::iterator iter; + while(!umessage.validate(iter)) { + auto next_char_iter=iter; + next_char_iter++; + umessage.replace(iter, next_char_iter, "?"); + } + + if(bold) + get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); + else + get_buffer()->insert(get_buffer()->end(), umessage); + + if(get_buffer()->get_line_count()>Singleton::config->terminal.history_size) { + int lines=get_buffer()->get_line_count()-Singleton::config->terminal.history_size; + get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); + deleted_lines+=static_cast(lines); + } + + return static_cast(get_buffer()->end().get_line())+deleted_lines; +} + +void Terminal::print(size_t line_nr, const std::string &message){ + if(line_nrget_iter_at_line(static_cast(line_nr-deleted_lines)); + while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {} + get_buffer()->insert(end_line_iter, umessage); +} + +std::shared_ptr Terminal::print_in_progress(std::string start_msg) { + std::shared_ptr in_progress=std::shared_ptr(new Terminal::InProgress(start_msg)); + return in_progress; +} + +void Terminal::async_print(const std::string &message, bool bold) { + async_print_strings_mutex.lock(); + bool dispatch=true; + if(async_print_strings.size()>0) + dispatch=false; + async_print_strings.emplace_back(message, bold); + async_print_strings_mutex.unlock(); + if(dispatch) + async_print_dispatcher(); +} + +void Terminal::async_print(int line_nr, const std::string &message) { + async_print_on_line_strings_mutex.lock(); + bool dispatch=true; + if(async_print_on_line_strings.size()>0) + dispatch=false; + async_print_on_line_strings.emplace_back(line_nr, message); + async_print_on_line_strings_mutex.unlock(); + if(dispatch) + async_print_on_line_dispatcher(); +} + +bool Terminal::on_key_press_event(GdkEventKey *event) { + processes_mutex.lock(); + if(processes.size()>0) { + get_buffer()->place_cursor(get_buffer()->end()); + auto unicode=gdk_keyval_to_unicode(event->keyval); + char chr=(char)unicode; + if(unicode>=32 && unicode<=126) { + stdin_buffer+=chr; + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); + } + else if(event->keyval==GDK_KEY_BackSpace) { + if(stdin_buffer.size()>0 && get_buffer()->get_char_count()>0) { + auto iter=get_buffer()->end(); + iter--; + stdin_buffer.pop_back(); + get_buffer()->erase(iter, get_buffer()->end()); + } + } + else if(event->keyval==GDK_KEY_Return) { + stdin_buffer+='\n'; + processes.back()->write(stdin_buffer.c_str(), stdin_buffer.size()); + get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); + stdin_buffer.clear(); + } + } + processes_mutex.unlock(); + return true; +} diff --git a/src/terminal.h b/src/terminal.h index edbba02..c122b36 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include "process.h" class Terminal : public Gtk::TextView { public: @@ -27,11 +27,11 @@ public: }; Terminal(); - int execute(const std::string &command, const boost::filesystem::path &path="", bool use_pipes=true); - int execute(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path=""); - void async_execute(const std::string &command, const boost::filesystem::path &path="", std::function callback=nullptr); - void kill_last_async_execute(bool force=false); - void kill_async_executes(bool force=false); + int process(const std::string &command, const boost::filesystem::path &path="", bool use_pipes=true); + int process(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path=""); + void async_process(const std::string &command, const boost::filesystem::path &path="", std::function callback=nullptr); + void kill_last_async_process(bool force=false); + void kill_async_processes(bool force=false); size_t print(const std::string &message, bool bold=false); void print(size_t line_nr, const std::string &message); @@ -49,12 +49,8 @@ private: std::mutex async_print_on_line_strings_mutex; Glib::RefPtr bold_tag; - std::mutex async_executes_mutex; -#ifdef _WIN32 - std::list > async_executes; -#else - std::list > async_executes; -#endif + std::vector > processes; + std::mutex processes_mutex; std::string stdin_buffer; size_t deleted_lines=0; diff --git a/src/terminal_unix.cc b/src/terminal_unix.cc deleted file mode 100644 index 2a1ee90..0000000 --- a/src/terminal_unix.cc +++ /dev/null @@ -1,440 +0,0 @@ -#include "terminal.h" -#include -#include "logging.h" -#include "singletons.h" -#include -#include - -#include //TODO: remove -using namespace std; //TODO: remove - -const ssize_t buffer_size=131072; - -//A working implementation of popen3, with all pipes getting closed properly. -//TODO: Eidheim is going to publish this one on his github, along with example uses -pid_t popen3(const std::string &command, const std::string &path, int *stdin_fd, int *stdout_fd, int *stderr_fd) { - pid_t pid; - int stdin_p[2], stdout_p[2], stderr_p[2]; - - if(stdin_fd!=nullptr && pipe(stdin_p)!=0) { - close(stdin_p[0]); - close(stdin_p[1]); - return -1; - } - if(stdout_fd!=nullptr && pipe(stdout_p)!=0) { - if(stdin_fd!=nullptr) close(stdin_p[0]); - if(stdin_fd!=nullptr) close(stdin_p[1]); - close(stdout_p[0]); - close(stdout_p[1]); - return -1; - } - if(stderr_fd!=nullptr && pipe(stderr_p)!=0) { - if(stdin_fd!=nullptr) close(stdin_p[0]); - if(stdin_fd!=nullptr) close(stdin_p[1]); - if(stdout_fd!=nullptr) close(stdout_p[0]); - if(stdout_fd!=nullptr) close(stdout_p[1]); - close(stderr_p[0]); - close(stderr_p[1]); - return -1; - } - - pid = fork(); - - if (pid < 0) { - if(stdin_fd!=nullptr) close(stdin_p[0]); - if(stdin_fd!=nullptr) close(stdin_p[1]); - if(stdout_fd!=nullptr) close(stdout_p[0]); - if(stdout_fd!=nullptr) close(stdout_p[1]); - if(stderr_fd!=nullptr) close(stderr_p[0]); - if(stderr_fd!=nullptr) close(stderr_p[1]); - return pid; - } - else if (pid == 0) { - if(stdin_fd!=nullptr) close(stdin_p[1]); - if(stdout_fd!=nullptr) close(stdout_p[0]); - if(stderr_fd!=nullptr) close(stderr_p[0]); - if(stdin_fd!=nullptr) dup2(stdin_p[0], 0); - if(stdout_fd!=nullptr) dup2(stdout_p[1], 1); - if(stderr_fd!=nullptr) dup2(stderr_p[1], 2); - - setpgid(0, 0); - //TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe - //TODO: One solution is: echo "command;exit"|script -q /dev/null - std::string cd_path_and_command; - if(path!="") { - cd_path_and_command="cd \""+path+"\" && "+command; - } - else - cd_path_and_command=command; - execl("/bin/sh", "sh", "-c", cd_path_and_command.c_str(), NULL); - perror("execl"); - exit(EXIT_FAILURE); - } - - if(stdin_fd!=nullptr) close(stdin_p[0]); - if(stdout_fd!=nullptr) close(stdout_p[1]); - if(stderr_fd!=nullptr) close(stderr_p[1]); - - if(stdin_fd!=nullptr) *stdin_fd = stdin_p[1]; - if(stdout_fd!=nullptr) *stdout_fd = stdout_p[0]; - if(stderr_fd!=nullptr) *stderr_fd = stderr_p[0]; - - return pid; -} - -Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { - waiting_print.connect([this](){ - Singleton::terminal->async_print(line_nr-1, "."); - }); - start(start_msg); -} - -Terminal::InProgress::~InProgress() { - stop=true; - if(wait_thread.joinable()) - wait_thread.join(); -} - -void Terminal::InProgress::start(const std::string& msg) { - line_nr=Singleton::terminal->print(msg+"...\n"); - wait_thread=std::thread([this](){ - size_t c=0; - while(!stop) { - if(c%100==0) - waiting_print(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - c++; - } - }); -} - -void Terminal::InProgress::done(const std::string& msg) { - if(!stop) { - stop=true; - Singleton::terminal->async_print(line_nr-1, msg); - } -} - -void Terminal::InProgress::cancel(const std::string& msg) { - if(!stop) { - stop=true; - Singleton::terminal->async_print(line_nr-1, msg); - } -} - -Terminal::Terminal() { - bold_tag=get_buffer()->create_tag(); - bold_tag->property_weight()=PANGO_WEIGHT_BOLD; - - async_print_dispatcher.connect([this](){ - async_print_strings_mutex.lock(); - if(async_print_strings.size()>0) { - for(auto &string_bold: async_print_strings) - print(string_bold.first, string_bold.second); - async_print_strings.clear(); - } - async_print_strings_mutex.unlock(); - }); - async_print_on_line_dispatcher.connect([this](){ - async_print_on_line_strings_mutex.lock(); - if(async_print_on_line_strings.size()>0) { - for(auto &line_string: async_print_on_line_strings) - print(line_string.first, line_string.second); - async_print_on_line_strings.clear(); - } - async_print_on_line_strings_mutex.unlock(); - }); -} - -int Terminal::execute(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { - int stdin_fd, stdout_fd, stderr_fd; - pid_t pid; - if(use_pipes) - pid=popen3(command, path.string(), &stdin_fd, &stdout_fd, &stderr_fd); - else - pid=popen3(command, path.string(), nullptr, nullptr, nullptr); - - if (pid<=0) { - async_print("Error: Failed to run command: " + command + "\n", true); - return -1; - } - else { - if(use_pipes) { - std::thread stderr_thread([this, stderr_fd](){ - char buffer[buffer_size]; - ssize_t n; - while ((n=read(stderr_fd, buffer, buffer_size)) > 0) { - std::string message; - message.reserve(n); - for(ssize_t c=0;c 0) { - std::string message; - message.reserve(n); - for(ssize_t c=0;c 0) { - std::string message; - message.reserve(n); - for(ssize_t c=0;c 0) { - Glib::ustring umessage; - umessage.reserve(n); - for(ssize_t c=0;c callback) { - std::thread async_execute_thread([this, command, path, callback](){ - int stdin_fd, stdout_fd, stderr_fd; - async_executes_mutex.lock(); - stdin_buffer.clear(); - auto pid=popen3(command, path.string(), &stdin_fd, &stdout_fd, &stderr_fd); - async_executes.emplace_back(pid, stdin_fd); - async_executes_mutex.unlock(); - - if (pid<=0) { - async_print("Error: Failed to run command: " + command + "\n", true); - if(callback) - callback(-1); - } - else { - std::thread stderr_thread([this, stderr_fd](){ - char buffer[buffer_size]; - ssize_t n; - while ((n=read(stderr_fd, buffer, buffer_size)) > 0) { - std::string message; - message.reserve(n); - for(ssize_t c=0;c 0) { - std::string message; - message.reserve(n); - for(ssize_t c=0;cfirst==pid) { - async_executes.erase(it); - break; - } - } - stdin_buffer.clear(); - close(stdin_fd); - close(stdout_fd); - close(stderr_fd); - async_executes_mutex.unlock(); - - if(callback) - callback(exit_code); - } - }); - async_execute_thread.detach(); -} - -void Terminal::kill_last_async_execute(bool force) { - async_executes_mutex.lock(); - if(async_executes.size()>0) { - if(force) - kill(-async_executes.back().first, SIGTERM); - else - kill(-async_executes.back().first, SIGINT); - } - async_executes_mutex.unlock(); -} - -void Terminal::kill_async_executes(bool force) { - async_executes_mutex.lock(); - for(auto &async_execute: async_executes) { - if(force) - kill(-async_execute.first, SIGTERM); - else - kill(-async_execute.first, SIGINT); - } - async_executes_mutex.unlock(); -} - -size_t Terminal::print(const std::string &message, bool bold){ - Glib::ustring umessage=message; - Glib::ustring::iterator iter; - while(!umessage.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - umessage.replace(iter, next_char_iter, "?"); - } - - if(bold) - get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); - else - get_buffer()->insert(get_buffer()->end(), umessage); - - if(get_buffer()->get_line_count()>Singleton::config->terminal.history_size) { - int lines=get_buffer()->get_line_count()-Singleton::config->terminal.history_size; - get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); - deleted_lines+=static_cast(lines); - } - - return static_cast(get_buffer()->end().get_line())+deleted_lines; -} - -void Terminal::print(size_t line_nr, const std::string &message){ - if(line_nrget_iter_at_line(static_cast(line_nr-deleted_lines)); - while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {} - get_buffer()->insert(end_line_iter, umessage); -} - -std::shared_ptr Terminal::print_in_progress(std::string start_msg) { - std::shared_ptr in_progress=std::shared_ptr(new Terminal::InProgress(start_msg)); - return in_progress; -} - -void Terminal::async_print(const std::string &message, bool bold) { - async_print_strings_mutex.lock(); - bool dispatch=true; - if(async_print_strings.size()>0) - dispatch=false; - async_print_strings.emplace_back(message, bold); - async_print_strings_mutex.unlock(); - if(dispatch) - async_print_dispatcher(); -} - -void Terminal::async_print(int line_nr, const std::string &message) { - async_print_on_line_strings_mutex.lock(); - bool dispatch=true; - if(async_print_on_line_strings.size()>0) - dispatch=false; - async_print_on_line_strings.emplace_back(line_nr, message); - async_print_on_line_strings_mutex.unlock(); - if(dispatch) - async_print_on_line_dispatcher(); -} - -bool Terminal::on_key_press_event(GdkEventKey *event) { - async_executes_mutex.lock(); - if(async_executes.size()>0) { - get_buffer()->place_cursor(get_buffer()->end()); - auto unicode=gdk_keyval_to_unicode(event->keyval); - char chr=(char)unicode; - if(unicode>=32 && unicode<=126) { - stdin_buffer+=chr; - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); - } - else if(event->keyval==GDK_KEY_BackSpace) { - if(stdin_buffer.size()>0 && get_buffer()->get_char_count()>0) { - auto iter=get_buffer()->end(); - iter--; - stdin_buffer.pop_back(); - get_buffer()->erase(iter, get_buffer()->end()); - } - } - else if(event->keyval==GDK_KEY_Return) { - stdin_buffer+='\n'; - write(async_executes.back().second, stdin_buffer.c_str(), stdin_buffer.size()); - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); - stdin_buffer.clear(); - } - } - async_executes_mutex.unlock(); - return true; -} diff --git a/src/terminal_win.cc b/src/terminal_win.cc deleted file mode 100644 index 962a55d..0000000 --- a/src/terminal_win.cc +++ /dev/null @@ -1,512 +0,0 @@ -#include "terminal.h" -#include -#include "logging.h" -#include "singletons.h" -#include -#include - -#include //TODO: remove -using namespace std; //TODO: remove - -const size_t buffer_size=131072; - -//Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx -//Note: on Windows it seems impossible to specify which pipes to use -//Thus, if stdin_h, stdout_h and stderr all are NULL, the out,err,in is sent to the parent process instead -HANDLE popen3(const std::string &command, const std::string &path, HANDLE *stdin_h, HANDLE *stdout_h, HANDLE *stderr_h) { - HANDLE g_hChildStd_IN_Rd = NULL; - HANDLE g_hChildStd_IN_Wr = NULL; - HANDLE g_hChildStd_OUT_Rd = NULL; - HANDLE g_hChildStd_OUT_Wr = NULL; - HANDLE g_hChildStd_ERR_Rd = NULL; - HANDLE g_hChildStd_ERR_Wr = NULL; - - SECURITY_ATTRIBUTES saAttr; - - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - saAttr.lpSecurityDescriptor = NULL; - - if(stdin_h!=nullptr) { - if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) - return NULL; - if(!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) { - CloseHandle(g_hChildStd_IN_Rd); - CloseHandle(g_hChildStd_IN_Wr); - return NULL; - } - } - if(stdout_h!=nullptr) { - if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) { - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Rd); - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Wr); - return NULL; - } - if(!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Rd); - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Wr); - CloseHandle(g_hChildStd_OUT_Rd); - CloseHandle(g_hChildStd_OUT_Wr); - return NULL; - } - } - if(stderr_h!=nullptr) { - if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0)) { - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Rd); - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Wr); - if(stdout_h!=nullptr) CloseHandle(g_hChildStd_OUT_Rd); - if(stdout_h!=nullptr) CloseHandle(g_hChildStd_OUT_Wr); - return NULL; - } - if(!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0)) { - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Rd); - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Wr); - if(stdout_h!=nullptr) CloseHandle(g_hChildStd_OUT_Rd); - if(stdout_h!=nullptr) CloseHandle(g_hChildStd_OUT_Wr); - CloseHandle(g_hChildStd_ERR_Rd); - CloseHandle(g_hChildStd_ERR_Wr); - return NULL; - } - } - - PROCESS_INFORMATION process_info; - STARTUPINFO siStartInfo; - - ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); - - ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); - siStartInfo.cb = sizeof(STARTUPINFO); - if(stdin_h!=nullptr) siStartInfo.hStdInput = g_hChildStd_IN_Rd; - if(stdout_h!=nullptr) siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; - if(stderr_h!=nullptr) siStartInfo.hStdError = g_hChildStd_ERR_Wr; - if(stdin_h!=nullptr || stdout_h!=nullptr || stderr_h!=nullptr) - siStartInfo.dwFlags |= STARTF_USESTDHANDLES; - - char* path_ptr; - if(path=="") - path_ptr=NULL; - else { - path_ptr=new char[path.size()+1]; - std::strcpy(path_ptr, path.c_str()); - } - char* command_cstr=new char[command.size()+1]; - std::strcpy(command_cstr, command.c_str()); - BOOL bSuccess = CreateProcess(NULL, - command_cstr, // command line - NULL, // process security attributes - NULL, // primary thread security attributes - TRUE, // handles are inherited - 0, // creation flags - NULL, // use parent's environment - path_ptr, // use parent's current directory - &siStartInfo, // STARTUPINFO pointer - &process_info); // receives PROCESS_INFORMATION - - if(!bSuccess) { - CloseHandle(process_info.hProcess); - CloseHandle(process_info.hThread); - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Rd); - if(stdout_h!=nullptr) CloseHandle(g_hChildStd_OUT_Wr); - if(stderr_h!=nullptr) CloseHandle(g_hChildStd_ERR_Wr); - return NULL; - } - else { - // Close handles to the child process and its primary thread. - // Some applications might keep these handles to monitor the status - // of the child process, for example. - - CloseHandle(process_info.hThread); - if(stdin_h!=nullptr) CloseHandle(g_hChildStd_IN_Rd); - if(stdout_h!=nullptr) CloseHandle(g_hChildStd_OUT_Wr); - if(stderr_h!=nullptr) CloseHandle(g_hChildStd_ERR_Wr); - } - - if(stdin_h!=NULL) *stdin_h=g_hChildStd_IN_Wr; - if(stdout_h!=NULL) *stdout_h=g_hChildStd_OUT_Rd; - if(stderr_h!=NULL) *stderr_h=g_hChildStd_ERR_Rd; - return process_info.hProcess; -} - -Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { - waiting_print.connect([this](){ - Singleton::terminal->async_print(line_nr-1, "."); - }); - start(start_msg); -} - -Terminal::InProgress::~InProgress() { - stop=true; - if(wait_thread.joinable()) - wait_thread.join(); -} - -void Terminal::InProgress::start(const std::string& msg) { - line_nr=Singleton::terminal->print(msg+"...\n"); - wait_thread=std::thread([this](){ - size_t c=0; - while(!stop) { - if(c%100==0) - waiting_print(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - c++; - } - }); -} - -void Terminal::InProgress::done(const std::string& msg) { - if(!stop) { - stop=true; - Singleton::terminal->async_print(line_nr-1, msg); - } -} - -void Terminal::InProgress::cancel(const std::string& msg) { - if(!stop) { - stop=true; - Singleton::terminal->async_print(line_nr-1, msg); - } -} - -Terminal::Terminal() { - bold_tag=get_buffer()->create_tag(); - bold_tag->property_weight()=PANGO_WEIGHT_BOLD; - - async_print_dispatcher.connect([this](){ - async_print_strings_mutex.lock(); - if(async_print_strings.size()>0) { - for(auto &string_bold: async_print_strings) - print(string_bold.first, string_bold.second); - async_print_strings.clear(); - } - async_print_strings_mutex.unlock(); - }); - async_print_on_line_dispatcher.connect([this](){ - async_print_on_line_strings_mutex.lock(); - if(async_print_on_line_strings.size()>0) { - for(auto &line_string: async_print_on_line_strings) - print(line_string.first, line_string.second); - async_print_on_line_strings.clear(); - } - async_print_on_line_strings_mutex.unlock(); - }); -} - -int Terminal::execute(const std::string &command, const boost::filesystem::path &path, bool use_pipes) { - HANDLE stdin_h, stdout_h, stderr_h; - - HANDLE process; - if(use_pipes) - process=popen3(command, path.string(), &stdin_h, &stdout_h, &stderr_h); - else - process=popen3(command, path.string(), nullptr, nullptr, nullptr); - if(process==NULL) { - async_print("Error: Failed to run command: " + command + "\n", true); - return -1; - } - if(use_pipes) { - std::thread stderr_thread([this, stderr_h](){ - DWORD n; - CHAR buffer[buffer_size]; - for (;;) { - BOOL bSuccess = ReadFile(stderr_h, buffer, static_cast(buffer_size), &n, NULL); - if(!bSuccess || n == 0) - break; - - std::string message; - message.reserve(n); - for(DWORD c=0;c(buffer_size), &n, NULL); - if(!bSuccess || n == 0) - break; - - std::string message; - message.reserve(n); - for(DWORD c=0;c(buffer_size), &n, NULL); - if(!bSuccess || n == 0) - break; - - std::string message; - message.reserve(n); - for(DWORD c=0;c(buffer_size), &n, NULL); - if(!bSuccess || n == 0) - break; - Glib::ustring umessage; - umessage.reserve(n); - for(DWORD c=0;c(n)); - } - }); - stdout_thread.detach(); - - CHAR buffer[buffer_size]; - for(;;) { - stdin_stream.readsome(buffer, buffer_size); - auto read_n=stdin_stream.gcount(); - if(read_n==0) - break; - DWORD write_n; - BOOL bSuccess = WriteFile(stdin_h, buffer, static_cast(read_n), &write_n, NULL); - if(!bSuccess || write_n==0) - break; - } - CloseHandle(stdin_h); - - unsigned long exit_code; - WaitForSingleObject(process, INFINITE); - GetExitCodeProcess(process, &exit_code); - - CloseHandle(process); - CloseHandle(stdout_h); - CloseHandle(stderr_h); - return exit_code; -} - -void Terminal::async_execute(const std::string &command, const boost::filesystem::path &path, std::function callback) { - std::thread async_execute_thread([this, command, path, callback](){ - HANDLE stdin_h, stdout_h, stderr_h; - - async_executes_mutex.lock(); - stdin_buffer.clear(); - auto process=popen3(command, path.string(), &stdin_h, &stdout_h, &stderr_h); - if(process==NULL) { - async_executes_mutex.unlock(); - async_print("Error: Failed to run command: " + command + "\n", true); - if(callback) - callback(-1); - return; - } - async_executes.emplace_back(process, stdin_h); - async_executes_mutex.unlock(); - - std::thread stderr_thread([this, stderr_h](){ - DWORD n; - CHAR buffer[buffer_size]; - for (;;) { - BOOL bSuccess = ReadFile(stderr_h, buffer, static_cast(buffer_size), &n, NULL); - if(!bSuccess || n == 0) - break; - - std::string message; - message.reserve(n); - for(DWORD c=0;c(buffer_size), &n, NULL); - if(!bSuccess || n == 0) - break; - - std::string message; - message.reserve(n); - for(DWORD c=0;cfirst==process) { - async_executes.erase(it); - break; - } - } - stdin_buffer.clear(); - CloseHandle(process); - CloseHandle(stdin_h); - CloseHandle(stdout_h); - CloseHandle(stderr_h); - async_executes_mutex.unlock(); - - if(callback) - callback(exit_code); - }); - async_execute_thread.detach(); -} - -void Terminal::kill_last_async_execute(bool force) { - async_executes_mutex.lock(); - if(async_executes.size()>0) { - TerminateProcess(async_executes.back().first, 2); - } - async_executes_mutex.unlock(); -} - -void Terminal::kill_async_executes(bool force) { - async_executes_mutex.lock(); - for(auto &async_execute: async_executes) { - TerminateProcess(async_execute.first, 2); - } - async_executes_mutex.unlock(); -} - -size_t Terminal::print(const std::string &message, bool bold){ - Glib::ustring umessage=message; - Glib::ustring::iterator iter; - while(!umessage.validate(iter)) { - auto next_char_iter=iter; - next_char_iter++; - umessage.replace(iter, next_char_iter, "?"); - } - - if(bold) - get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); - else - get_buffer()->insert(get_buffer()->end(), umessage); - - if(get_buffer()->get_line_count()>Singleton::config->terminal.history_size) { - int lines=get_buffer()->get_line_count()-Singleton::config->terminal.history_size; - get_buffer()->erase(get_buffer()->begin(), get_buffer()->get_iter_at_line(lines)); - deleted_lines+=static_cast(lines); - } - - return static_cast(get_buffer()->end().get_line())+deleted_lines; -} - -void Terminal::print(size_t line_nr, const std::string &message){ - if(line_nrget_iter_at_line(static_cast(line_nr-deleted_lines)); - while(!end_line_iter.ends_line() && end_line_iter.forward_char()) {} - get_buffer()->insert(end_line_iter, umessage); -} - -std::shared_ptr Terminal::print_in_progress(std::string start_msg) { - std::shared_ptr in_progress=std::shared_ptr(new Terminal::InProgress(start_msg)); - return in_progress; -} - -void Terminal::async_print(const std::string &message, bool bold) { - async_print_strings_mutex.lock(); - bool dispatch=true; - if(async_print_strings.size()>0) - dispatch=false; - async_print_strings.emplace_back(message, bold); - async_print_strings_mutex.unlock(); - if(dispatch) - async_print_dispatcher(); -} - -void Terminal::async_print(int line_nr, const std::string &message) { - async_print_on_line_strings_mutex.lock(); - bool dispatch=true; - if(async_print_on_line_strings.size()>0) - dispatch=false; - async_print_on_line_strings.emplace_back(line_nr, message); - async_print_on_line_strings_mutex.unlock(); - if(dispatch) - async_print_on_line_dispatcher(); -} - -bool Terminal::on_key_press_event(GdkEventKey *event) { - async_executes_mutex.lock(); - if(async_executes.size()>0) { - get_buffer()->place_cursor(get_buffer()->end()); - auto unicode=gdk_keyval_to_unicode(event->keyval); - char chr=(char)unicode; - if(unicode>=32 && unicode<=126) { - stdin_buffer+=chr; - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); - } - else if(event->keyval==GDK_KEY_BackSpace) { - if(stdin_buffer.size()>0 && get_buffer()->get_char_count()>0) { - auto iter=get_buffer()->end(); - iter--; - stdin_buffer.pop_back(); - get_buffer()->erase(iter, get_buffer()->end()); - } - } - else if(event->keyval==GDK_KEY_Return) { - stdin_buffer+='\n'; - DWORD written; - WriteFile(async_executes.back().second, stdin_buffer.c_str(), stdin_buffer.size(), &written, NULL); - //TODO: is this line needed? - get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); - stdin_buffer.clear(); - } - } - async_executes_mutex.unlock(); - return true; -} diff --git a/src/window.cc b/src/window.cc index c9f91dc..dd7ab73 100644 --- a/src/window.cc +++ b/src/window.cc @@ -370,7 +370,7 @@ void Window::set_menu_actions() { if(query!=documentation_search->second.queries.end()) { std::string uri=query->second+token_query; #ifdef __APPLE__ - Singleton::terminal->execute("open \""+uri+"\""); + Singleton::terminal->process("open \""+uri+"\""); #else GError* error=NULL; gtk_show_uri(NULL, uri.c_str(), GDK_CURRENT_TIME, &error); @@ -527,7 +527,7 @@ void Window::set_menu_actions() { compiling=true; Singleton::terminal->print("Compiling and running "+executable_path.string()+"\n"); auto project_path=cmake.project_path; - Singleton::terminal->async_execute(Singleton::config->terminal.make_command, cmake.project_path, [this, executable_path, project_path](int exit_code){ + Singleton::terminal->async_process(Singleton::config->terminal.make_command, cmake.project_path, [this, executable_path, project_path](int exit_code){ compiling=false; if(exit_code==EXIT_SUCCESS) { auto executable_path_spaces_fixed=executable_path.string(); @@ -539,7 +539,7 @@ void Window::set_menu_actions() { } last_char=executable_path_spaces_fixed[c]; } - Singleton::terminal->async_execute(executable_path_spaces_fixed, project_path, [this, executable_path](int exit_code){ + Singleton::terminal->async_process(executable_path_spaces_fixed, project_path, [this, executable_path](int exit_code){ Singleton::terminal->async_print(executable_path.string()+" returned: "+std::to_string(exit_code)+'\n'); }); } @@ -566,7 +566,7 @@ void Window::set_menu_actions() { if(cmake.project_path!="") { compiling=true; Singleton::terminal->print("Compiling project "+cmake.project_path.string()+"\n"); - Singleton::terminal->async_execute(Singleton::config->terminal.make_command, cmake.project_path, [this](int exit_code){ + Singleton::terminal->async_process(Singleton::config->terminal.make_command, cmake.project_path, [this](int exit_code){ compiling=false; }); } @@ -586,7 +586,7 @@ void Window::set_menu_actions() { auto run_path=notebook.get_current_folder(); Singleton::terminal->async_print("Running: "+content+'\n'); - Singleton::terminal->async_execute(content, run_path, [this, content](int exit_code){ + Singleton::terminal->async_process(content, run_path, [this, content](int exit_code){ Singleton::terminal->async_print(content+" returned: "+std::to_string(exit_code)+'\n'); }); } @@ -601,10 +601,10 @@ void Window::set_menu_actions() { }); menu->add_action("kill_last_running", [this]() { - Singleton::terminal->kill_last_async_execute(); + Singleton::terminal->kill_last_async_process(); }); menu->add_action("force_kill_last_running", [this]() { - Singleton::terminal->kill_last_async_execute(true); + Singleton::terminal->kill_last_async_process(true); }); menu->add_action("next_tab", [this]() { @@ -688,7 +688,7 @@ bool Window::on_delete_event(GdkEventAny *event) { if(!notebook.close_current_page()) return true; } - Singleton::terminal->kill_async_executes(); + Singleton::terminal->kill_async_processes(); return false; }