From 18ead2f820b61fbb5625aee6f4131197fc4cf834 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 4 Dec 2015 09:45:33 +0100 Subject: [PATCH 1/9] Terminal and process cleanup. Added Process class with unix implementation, Windows implementation coming soon. --- src/CMakeLists.txt | 7 +- src/cmake.cc | 2 +- src/juci.cc | 2 +- src/process.cc | 21 ++ src/process.h | 52 +++++ src/process_unix.cc | 151 +++++++++++++ src/process_win.cc | 3 + src/source_clang.cc | 2 +- src/terminal.cc | 273 +++++++++++++++++++++++ src/terminal.h | 20 +- src/terminal_unix.cc | 440 ------------------------------------- src/terminal_win.cc | 512 ------------------------------------------- src/window.cc | 16 +- 13 files changed, 524 insertions(+), 977 deletions(-) create mode 100644 src/process.cc create mode 100644 src/process.h create mode 100644 src/process_unix.cc create mode 100644 src/process_win.cc create mode 100644 src/terminal.cc delete mode 100644 src/terminal_unix.cc delete mode 100644 src/terminal_win.cc 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; } From ccd0251179d73661cc741b7f1061539ae972edce Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 4 Dec 2015 09:59:08 +0100 Subject: [PATCH 2/9] Added missing include in process_unix.cc --- src/process_unix.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/process_unix.cc b/src/process_unix.cc index 068a817..84924ea 100644 --- a/src/process_unix.cc +++ b/src/process_unix.cc @@ -1,6 +1,7 @@ #include "process.h" #include #include +#include #include #include //TODO: remove From e7c70ac1be1f21baace1f40be795095193f2a6a0 Mon Sep 17 00:00:00 2001 From: "U-olece-PC\\olece" Date: Fri, 4 Dec 2015 11:43:38 +0100 Subject: [PATCH 3/9] Added Windows process support. Also temporarily using dialogs_unix on Windows because of missing function in MSYS2. --- src/CMakeLists.txt | 2 +- src/process.h | 12 +-- src/process_unix.cc | 11 ++- src/process_win.cc | 208 +++++++++++++++++++++++++++++++++++++++++++- src/terminal.cc | 2 +- src/terminal.h | 2 +- 6 files changed, 220 insertions(+), 17 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d9c3b0..b81bca4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -94,7 +94,7 @@ set(source_files juci.h if(MSYS) list(APPEND source_files process_win.cc) - list(APPEND source_files dialogs_win.cc) + list(APPEND source_files dialogs_unix.cc) #dialogs_win.cc does not work any more because of missing SHCreateItemFromParsingName else() list(APPEND source_files process_unix.cc) list(APPEND source_files dialogs_unix.cc) diff --git a/src/process.h b/src/process.h index 3c8bfe2..ca05c64 100644 --- a/src/process.h +++ b/src/process.h @@ -1,19 +1,19 @@ #ifndef JUCI_PROCESS_H_ #define JUCI_PROCESS_H_ -#include #include #include #include #include #include - #ifdef _WIN32 - -#else +#include + typedef HANDLE process_id_type; + typedef HANDLE file_descriptor_type; +#else +#include typedef pid_t process_id_type; typedef int file_descriptor_type; - typedef int exit_code_type; #endif class Process { @@ -28,7 +28,7 @@ public: ///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(); + int get_exit_code(); bool write(const char *bytes, size_t n); ///Kill a given process id. diff --git a/src/process_unix.cc b/src/process_unix.cc index 84924ea..74bbe30 100644 --- a/src/process_unix.cc +++ b/src/process_unix.cc @@ -1,19 +1,18 @@ #include "process.h" #include -#include #include #include #include //TODO: remove using namespace std; //TODO: remove -pid_t Process::open(const std::string &command, const std::string &path) { +process_id_type Process::open(const std::string &command, const std::string &path) { if(use_stdin) - stdin_fd=std::unique_ptr(new int); + stdin_fd=std::unique_ptr(new file_descriptor_type); if(read_stdout) - stdout_fd=std::unique_ptr(new int); + stdout_fd=std::unique_ptr(new file_descriptor_type); if(read_stderr) - stderr_fd=std::unique_ptr(new int); + stderr_fd=std::unique_ptr(new file_descriptor_type); int stdin_p[2], stdout_p[2], stderr_p[2]; @@ -39,7 +38,7 @@ pid_t Process::open(const std::string &command, const std::string &path) { return -1; } - pid_t pid = fork(); + process_id_type pid = fork(); if (pid < 0) { if(stdin_fd) close(stdin_p[0]); diff --git a/src/process_win.cc b/src/process_win.cc index b61c0db..5fa2e38 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -1,3 +1,207 @@ #include "process.h" -#include -#include +#include + +#include //TODO: remove +using namespace std; //TODO: remove + +//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 +process_id_type Process::open(const std::string &command, const std::string &path) { + if(use_stdin) + stdin_fd=std::unique_ptr(new file_descriptor_type); + if(read_stdout) + stdout_fd=std::unique_ptr(new file_descriptor_type); + if(read_stderr) + stderr_fd=std::unique_ptr(new file_descriptor_type); + + 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_fd) { + 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_fd) { + if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) { + if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); + if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); + return NULL; + } + if(!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { + if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); + if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); + CloseHandle(g_hChildStd_OUT_Rd); + CloseHandle(g_hChildStd_OUT_Wr); + return NULL; + } + } + if(stderr_fd) { + if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0)) { + if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); + if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); + if(stdout_fd) CloseHandle(g_hChildStd_OUT_Rd); + if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); + return NULL; + } + if(!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0)) { + if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); + if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); + if(stdout_fd) CloseHandle(g_hChildStd_OUT_Rd); + if(stdout_fd) 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_fd) siStartInfo.hStdInput = g_hChildStd_IN_Rd; + if(stdout_fd) siStartInfo.hStdOutput = g_hChildStd_OUT_Wr; + if(stderr_fd) siStartInfo.hStdError = g_hChildStd_ERR_Wr; + if(stdin_fd || stdout_fd || stderr_fd) + 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_fd) CloseHandle(g_hChildStd_IN_Rd); + if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); + if(stderr_fd) 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_fd) CloseHandle(g_hChildStd_IN_Rd); + if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); + if(stderr_fd) CloseHandle(g_hChildStd_ERR_Wr); + } + + if(stdin_fd) *stdin_fd=g_hChildStd_IN_Wr; + if(stdout_fd) *stdout_fd=g_hChildStd_OUT_Rd; + if(stderr_fd) *stderr_fd=g_hChildStd_ERR_Rd; + return process_info.hProcess; +} + +void Process::async_read() { + if(stdout_fd) { + stdout_thread=std::thread([this](){ + DWORD n; + char buffer[buffer_size]; + for (;;) { + BOOL bSuccess = ReadFile(*stdout_fd, static_cast(buffer), static_cast(buffer_size), &n, NULL); + if(!bSuccess || n == 0) + break; + read_stdout(buffer, static_cast(n)); + } + }); + } + if(stderr_fd) { + stderr_thread=std::thread([this](){ + DWORD n; + char buffer[buffer_size]; + for (;;) { + BOOL bSuccess = ReadFile(*stderr_fd, static_cast(buffer), static_cast(buffer_size), &n, NULL); + if(!bSuccess || n == 0) + break; + read_stderr(buffer, static_cast(n)); + } + }); + } +} + +int Process::get_exit_code() { + DWORD exit_code; + WaitForSingleObject(id, INFINITE); + GetExitCodeProcess(id, &exit_code); + CloseHandle(id); + + if(stdout_thread.joinable()) + stdout_thread.join(); + if(stderr_thread.joinable()) + stderr_thread.join(); + + stdin_mutex.lock(); + if(stdin_fd) { + CloseHandle(*stdin_fd); + stdin_fd.reset(); + } + stdin_mutex.unlock(); + if(stdout_fd) { + CloseHandle(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + CloseHandle(*stderr_fd); + stderr_fd.reset(); + } + + return static_cast(exit_code); +} + +bool Process::write(const char *bytes, size_t n) { + stdin_mutex.lock(); + if(stdin_fd) { + DWORD written; + BOOL bSuccess=WriteFile(id, bytes, static_cast(n), &written, NULL); + if(!bSuccess || written==0) { + stdin_mutex.unlock(); + return false; + } + else { + stdin_mutex.unlock(); + return true; + } + } + stdin_mutex.unlock(); + return false; +} + +void Process::kill(process_id_type id, bool force) { + TerminateProcess(id, 2); +} diff --git a/src/terminal.cc b/src/terminal.cc index e9c8d42..a8cc032 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -144,7 +144,7 @@ void Terminal::async_process(const std::string &command, const boost::filesystem processes_mutex.unlock(); } - exit_code_type exit_code=process->get_exit_code(); + auto exit_code=process->get_exit_code(); processes_mutex.lock(); for(auto it=processes.begin();it!=processes.end();it++) { diff --git a/src/terminal.h b/src/terminal.h index c122b36..2ab0fe8 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -29,7 +29,7 @@ public: Terminal(); 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 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); From 2b8e72944703d2b391975069d971098899fdf874 Mon Sep 17 00:00:00 2001 From: "U-olece-PC\\olece" Date: Fri, 4 Dec 2015 14:04:40 +0100 Subject: [PATCH 4/9] Fixes to windows processing and some general processing issues. --- src/CMakeLists.txt | 2 +- src/process.cc | 2 +- src/process.h | 2 +- src/process_win.cc | 69 ++++++++++++++++++++++++++++++++++------------ 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b81bca4..67eeecc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,7 +22,7 @@ if(UNIX) #Checking if compiling on Ubuntu that has a buggy menu system endif() if(MSYS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJUCI_CMAKE_INSTALL_PREFIX=\\\"${CMAKE_INSTALL_PREFIX}\\\"") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSYS2_PROCESS_USE_SH -DJUCI_CMAKE_INSTALL_PREFIX=\\\"${CMAKE_INSTALL_PREFIX}\\\"") endif() INCLUDE(FindPkgConfig) diff --git a/src/process.cc b/src/process.cc index 7e20fe6..2391ab3 100644 --- a/src/process.cc +++ b/src/process.cc @@ -9,7 +9,7 @@ Process::Process(const std::string &command, const std::string &path, 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) + if(id!=0) async_read(); } diff --git a/src/process.h b/src/process.h index ca05c64..66019b1 100644 --- a/src/process.h +++ b/src/process.h @@ -8,7 +8,7 @@ #include #ifdef _WIN32 #include - typedef HANDLE process_id_type; + typedef DWORD process_id_type; typedef HANDLE file_descriptor_type; #else #include diff --git a/src/process_win.cc b/src/process_win.cc index 5fa2e38..771c377 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -1,5 +1,6 @@ #include "process.h" #include +#include "TlHelp32.h" #include //TODO: remove using namespace std; //TODO: remove @@ -30,25 +31,25 @@ process_id_type Process::open(const std::string &command, const std::string &pat if(stdin_fd) { if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) - return NULL; + return 0; if(!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) { CloseHandle(g_hChildStd_IN_Rd); CloseHandle(g_hChildStd_IN_Wr); - return NULL; + return 0; } } if(stdout_fd) { if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) { if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); - return NULL; + return 0; } if(!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) { if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); CloseHandle(g_hChildStd_OUT_Rd); CloseHandle(g_hChildStd_OUT_Wr); - return NULL; + return 0; } } if(stderr_fd) { @@ -57,7 +58,7 @@ process_id_type Process::open(const std::string &command, const std::string &pat if(stdin_fd) CloseHandle(g_hChildStd_IN_Wr); if(stdout_fd) CloseHandle(g_hChildStd_OUT_Rd); if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); - return NULL; + return 0; } if(!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0)) { if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); @@ -66,7 +67,7 @@ process_id_type Process::open(const std::string &command, const std::string &pat if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); CloseHandle(g_hChildStd_ERR_Rd); CloseHandle(g_hChildStd_ERR_Wr); - return NULL; + return 0; } } @@ -90,8 +91,27 @@ process_id_type Process::open(const std::string &command, const std::string &pat path_ptr=new char[path.size()+1]; std::strcpy(path_ptr, path.c_str()); } - char* command_cstr=new char[command.size()+1]; + + char* command_cstr; +#ifdef MSYS2_PROCESS_USE_SH + size_t pos=0; + std::string sh_command=command; + while((pos=sh_command.find('\"', pos))!=std::string::npos) { + if(pos>0 && sh_command[pos-1]!='\\') { + sh_command.replace(pos, 1, "\\\""); + pos++; + } + pos++; + } + sh_command.insert(0, "sh -c \""); + sh_command+="\""; + command_cstr=new char[sh_command.size()+1]; + std::strcpy(command_cstr, sh_command.c_str()); +#else + command_cstr=new char[command.size()+1]; std::strcpy(command_cstr, command.c_str()); +#endif + BOOL bSuccess = CreateProcess(NULL, command_cstr, // command line NULL, // process security attributes @@ -109,13 +129,9 @@ process_id_type Process::open(const std::string &command, const std::string &pat if(stdin_fd) CloseHandle(g_hChildStd_IN_Rd); if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); if(stderr_fd) CloseHandle(g_hChildStd_ERR_Wr); - return NULL; + return 0; } 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_fd) CloseHandle(g_hChildStd_IN_Rd); if(stdout_fd) CloseHandle(g_hChildStd_OUT_Wr); @@ -125,7 +141,8 @@ process_id_type Process::open(const std::string &command, const std::string &pat if(stdin_fd) *stdin_fd=g_hChildStd_IN_Wr; if(stdout_fd) *stdout_fd=g_hChildStd_OUT_Rd; if(stderr_fd) *stderr_fd=g_hChildStd_ERR_Rd; - return process_info.hProcess; + + return process_info.dwProcessId; } void Process::async_read() { @@ -157,9 +174,10 @@ void Process::async_read() { int Process::get_exit_code() { DWORD exit_code; - WaitForSingleObject(id, INFINITE); - GetExitCodeProcess(id, &exit_code); - CloseHandle(id); + HANDLE process_info = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); + WaitForSingleObject(process_info, INFINITE); + GetExitCodeProcess(process_info, &exit_code); + CloseHandle(process_info); if(stdout_thread.joinable()) stdout_thread.join(); @@ -188,7 +206,7 @@ bool Process::write(const char *bytes, size_t n) { stdin_mutex.lock(); if(stdin_fd) { DWORD written; - BOOL bSuccess=WriteFile(id, bytes, static_cast(n), &written, NULL); + BOOL bSuccess=WriteFile(*stdin_fd, bytes, static_cast(n), &written, NULL); if(!bSuccess || written==0) { stdin_mutex.unlock(); return false; @@ -203,5 +221,20 @@ bool Process::write(const char *bytes, size_t n) { } void Process::kill(process_id_type id, bool force) { - TerminateProcess(id, 2); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(snapshot) { + PROCESSENTRY32 process; + ZeroMemory(&process, sizeof(process)); + process.dwSize = sizeof(process); + if(Process32First(snapshot, &process)) { + do { + if(process.th32ParentProcessID==id) { + HANDLE process_info = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); + if(process_info) TerminateProcess(process_info, 2); + } + } while (Process32Next(snapshot, &process)); + } + } + HANDLE process_info = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); + if(process_info) TerminateProcess(process_info, 2); } From 1a7c0831be55d176e7a5e256e7a6509896de4753 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 4 Dec 2015 14:12:43 +0100 Subject: [PATCH 5/9] Renamed MSYS2_PROCESS_USE_SH to MSYS_PROCESS_USE_SH --- src/CMakeLists.txt | 2 +- src/process_win.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 67eeecc..3fb34c1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,7 +22,7 @@ if(UNIX) #Checking if compiling on Ubuntu that has a buggy menu system endif() if(MSYS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSYS2_PROCESS_USE_SH -DJUCI_CMAKE_INSTALL_PREFIX=\\\"${CMAKE_INSTALL_PREFIX}\\\"") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSYS_PROCESS_USE_SH -DJUCI_CMAKE_INSTALL_PREFIX=\\\"${CMAKE_INSTALL_PREFIX}\\\"") endif() INCLUDE(FindPkgConfig) diff --git a/src/process_win.cc b/src/process_win.cc index 771c377..a4cc727 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -93,7 +93,7 @@ process_id_type Process::open(const std::string &command, const std::string &pat } char* command_cstr; -#ifdef MSYS2_PROCESS_USE_SH +#ifdef MSYS_PROCESS_USE_SH size_t pos=0; std::string sh_command=command; while((pos=sh_command.find('\"', pos))!=std::string::npos) { From d7a48e30fe5aa58728c4a67f583bcfd9ab9a73d4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 4 Dec 2015 16:06:16 +0100 Subject: [PATCH 6/9] Fixed some issues with the new terminal/process source --- src/process.cc | 4 ++-- src/process.h | 9 ++++++--- src/process_unix.cc | 20 ++++++++++++-------- src/process_win.cc | 20 ++++++++++++-------- src/terminal.cc | 10 ++++++---- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/process.cc b/src/process.cc index 2391ab3..72a4906 100644 --- a/src/process.cc +++ b/src/process.cc @@ -6,8 +6,8 @@ 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) { + bool open_stdin, size_t buffer_size): + read_stdout(read_stdout), read_stderr(read_stderr), open_stdin(open_stdin), buffer_size(buffer_size) { id=open(command, path); if(id!=0) async_read(); diff --git a/src/process.h b/src/process.h index 66019b1..79d0e68 100644 --- a/src/process.h +++ b/src/process.h @@ -21,7 +21,7 @@ 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, + bool open_stdin=false, size_t buffer_size=131072); ~Process(); @@ -29,7 +29,10 @@ public: process_id_type get_id() {return id;} ///Wait until process is finished, and return exit_code. int get_exit_code(); - bool write(const char *bytes, size_t n); + ///Write to stdin. + bool write_stdin(const char *bytes, size_t n); + ///Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. + void close_stdin(); ///Kill a given process id. static void kill(process_id_type id, bool force=false); @@ -38,7 +41,7 @@ private: std::function read_stdout; std::function read_stderr; std::thread stdout_thread, stderr_thread; - bool use_stdin; + bool open_stdin; std::mutex stdin_mutex; const size_t buffer_size; diff --git a/src/process_unix.cc b/src/process_unix.cc index 74bbe30..f7e1fb5 100644 --- a/src/process_unix.cc +++ b/src/process_unix.cc @@ -7,7 +7,7 @@ using namespace std; //TODO: remove process_id_type Process::open(const std::string &command, const std::string &path) { - if(use_stdin) + if(open_stdin) stdin_fd=std::unique_ptr(new file_descriptor_type); if(read_stdout) stdout_fd=std::unique_ptr(new file_descriptor_type); @@ -109,12 +109,7 @@ int Process::get_exit_code() { if(stderr_thread.joinable()) stderr_thread.join(); - stdin_mutex.lock(); - if(stdin_fd) { - close(*stdin_fd); - stdin_fd.reset(); - } - stdin_mutex.unlock(); + close_stdin(); if(stdout_fd) { close(*stdout_fd); stdout_fd.reset(); @@ -127,7 +122,7 @@ int Process::get_exit_code() { return exit_code; } -bool Process::write(const char *bytes, size_t n) { +bool Process::write_stdin(const char *bytes, size_t n) { stdin_mutex.lock(); if(stdin_fd) { if(::write(*stdin_fd, bytes, n)>=0) { @@ -143,6 +138,15 @@ bool Process::write(const char *bytes, size_t n) { return false; } +void Process::close_stdin() { + stdin_mutex.lock(); + if(stdin_fd) { + close(*stdin_fd); + stdin_fd.reset(); + } + stdin_mutex.unlock(); +} + void Process::kill(process_id_type id, bool force) { if(force) ::kill(-id, SIGTERM); diff --git a/src/process_win.cc b/src/process_win.cc index a4cc727..6487adf 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -9,7 +9,7 @@ using namespace std; //TODO: remove //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 process_id_type Process::open(const std::string &command, const std::string &path) { - if(use_stdin) + if(open_stdin) stdin_fd=std::unique_ptr(new file_descriptor_type); if(read_stdout) stdout_fd=std::unique_ptr(new file_descriptor_type); @@ -184,12 +184,7 @@ int Process::get_exit_code() { if(stderr_thread.joinable()) stderr_thread.join(); - stdin_mutex.lock(); - if(stdin_fd) { - CloseHandle(*stdin_fd); - stdin_fd.reset(); - } - stdin_mutex.unlock(); + close_stdin(); if(stdout_fd) { CloseHandle(*stdout_fd); stdout_fd.reset(); @@ -202,7 +197,7 @@ int Process::get_exit_code() { return static_cast(exit_code); } -bool Process::write(const char *bytes, size_t n) { +bool Process::write_stdin(const char *bytes, size_t n) { stdin_mutex.lock(); if(stdin_fd) { DWORD written; @@ -220,6 +215,15 @@ bool Process::write(const char *bytes, size_t n) { return false; } +void Process::close_stdin() { + stdin_mutex.lock(); + if(stdin_fd) { + CloseHandle(*stdin_fd); + stdin_fd.reset(); + } + stdin_mutex.unlock(); +} + void Process::kill(process_id_type id, bool force) { HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(snapshot) { diff --git a/src/terminal.cc b/src/terminal.cc index a8cc032..b271279 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -92,7 +92,7 @@ int Terminal::process(const std::string &command, const boost::filesystem::path 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 umessage(std::string(bytes, n)); Glib::ustring::iterator iter; while(!umessage.validate(iter)) { auto next_char_iter=iter; @@ -102,7 +102,7 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c stdout_stream.write(umessage.data(), n); }, [this](const char* bytes, size_t n) { async_print(std::string(bytes, n), true); - }); + }, true); if(process.get_id()<=0) { async_print("Error: Failed to run command: " + command + "\n", true); @@ -115,9 +115,11 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c auto read_n=stdin_stream.gcount(); if(read_n==0) break; - if(!process.write(buffer, read_n)) + if(!process.write_stdin(buffer, read_n)) { break; + } } + process.close_stdin(); return process.get_exit_code(); } @@ -263,7 +265,7 @@ bool Terminal::on_key_press_event(GdkEventKey *event) { } else if(event->keyval==GDK_KEY_Return) { stdin_buffer+='\n'; - processes.back()->write(stdin_buffer.c_str(), stdin_buffer.size()); + processes.back()->write_stdin(stdin_buffer.c_str(), stdin_buffer.size()); get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); stdin_buffer.clear(); } From 29f2cc2f557a46e65521b118032358fb0eab2ba0 Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 4 Dec 2015 17:48:51 +0100 Subject: [PATCH 7/9] Some cleanups to process* --- src/process.cc | 2 +- src/process.h | 26 ++++++++++++++-------- src/process_unix.cc | 16 +++++++++----- src/process_win.cc | 53 +++++++++++++++++++++++---------------------- 4 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/process.cc b/src/process.cc index 72a4906..0ab07f0 100644 --- a/src/process.cc +++ b/src/process.cc @@ -9,7 +9,7 @@ Process::Process(const std::string &command, const std::string &path, bool open_stdin, size_t buffer_size): read_stdout(read_stdout), read_stderr(read_stderr), open_stdin(open_stdin), buffer_size(buffer_size) { id=open(command, path); - if(id!=0) + if(id>0) async_read(); } diff --git a/src/process.h b/src/process.h index 79d0e68..88b69ab 100644 --- a/src/process.h +++ b/src/process.h @@ -8,16 +8,24 @@ #include #ifdef _WIN32 #include - typedef DWORD process_id_type; - typedef HANDLE file_descriptor_type; #else #include - typedef pid_t process_id_type; - typedef int file_descriptor_type; #endif +///Create a new process given command and run path. +///Note: on Windows it seems impossible to specify which pipes to use. +///Thus, if read_stdout=nullptr, read_stderr=nullptr and open_std=false, +///the stdout, stderr and stdin are sent to the parent process instead. +///Compile with -DMSYS_PROCESS_USE_SH to run command using "sh -c [command]" on Windows as well. class Process { public: +#ifdef _WIN32 + typedef DWORD id_type; //Process id type + typedef HANDLE fd_type; //File descriptor type +#else + typedef pid_t id_type; + typedef int fd_type; +#endif Process(const std::string &command, const std::string &path=std::string(), std::function read_stdout=nullptr, std::function read_stderr=nullptr, @@ -26,7 +34,7 @@ public: ~Process(); ///Get the process id of the started process. - process_id_type get_id() {return id;} + id_type get_id() {return id;} ///Wait until process is finished, and return exit_code. int get_exit_code(); ///Write to stdin. @@ -35,7 +43,7 @@ public: void close_stdin(); ///Kill a given process id. - static void kill(process_id_type id, bool force=false); + static void kill(id_type id, bool force=false); private: std::function read_stdout; @@ -45,10 +53,10 @@ private: std::mutex stdin_mutex; const size_t buffer_size; - std::unique_ptr stdout_fd, stderr_fd, stdin_fd; + 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; + id_type open(const std::string &command, const std::string &path); + id_type id; void async_read(); }; diff --git a/src/process_unix.cc b/src/process_unix.cc index f7e1fb5..14e99ae 100644 --- a/src/process_unix.cc +++ b/src/process_unix.cc @@ -6,13 +6,13 @@ #include //TODO: remove using namespace std; //TODO: remove -process_id_type Process::open(const std::string &command, const std::string &path) { +Process::id_type Process::open(const std::string &command, const std::string &path) { if(open_stdin) - stdin_fd=std::unique_ptr(new file_descriptor_type); + stdin_fd=std::unique_ptr(new fd_type); if(read_stdout) - stdout_fd=std::unique_ptr(new file_descriptor_type); + stdout_fd=std::unique_ptr(new fd_type); if(read_stderr) - stderr_fd=std::unique_ptr(new file_descriptor_type); + stderr_fd=std::unique_ptr(new fd_type); int stdin_p[2], stdout_p[2], stderr_p[2]; @@ -38,7 +38,7 @@ process_id_type Process::open(const std::string &command, const std::string &pat return -1; } - process_id_type pid = fork(); + id_type pid = fork(); if (pid < 0) { if(stdin_fd) close(stdin_p[0]); @@ -101,6 +101,8 @@ void Process::async_read() { } int Process::get_exit_code() { + if(id<=0) + return -1; int exit_code; waitpid(id, &exit_code, 0); @@ -147,7 +149,9 @@ void Process::close_stdin() { stdin_mutex.unlock(); } -void Process::kill(process_id_type id, bool force) { +void Process::kill(id_type id, bool force) { + if(id<=0) + return; if(force) ::kill(-id, SIGTERM); else diff --git a/src/process_win.cc b/src/process_win.cc index 6487adf..c2798ff 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -5,16 +5,16 @@ #include //TODO: remove using namespace std; //TODO: remove -//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 -process_id_type Process::open(const std::string &command, const std::string &path) { +//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 read_stdout=nullptr, read_stderr=nullptr and open_std=false, the stdout, stderr and stdin are sent to the parent process instead. +Process::id_type Process::open(const std::string &command, const std::string &path) { if(open_stdin) - stdin_fd=std::unique_ptr(new file_descriptor_type); + stdin_fd=std::unique_ptr(new fd_type); if(read_stdout) - stdout_fd=std::unique_ptr(new file_descriptor_type); + stdout_fd=std::unique_ptr(new fd_type); if(read_stderr) - stderr_fd=std::unique_ptr(new file_descriptor_type); + stderr_fd=std::unique_ptr(new fd_type); HANDLE g_hChildStd_IN_Rd = NULL; HANDLE g_hChildStd_IN_Wr = NULL; @@ -112,16 +112,8 @@ process_id_type Process::open(const std::string &command, const std::string &pat std::strcpy(command_cstr, command.c_str()); #endif - 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 + BOOL bSuccess = CreateProcess(NULL, command_cstr, NULL, NULL, TRUE, 0, + NULL, path_ptr, &siStartInfo, &process_info); if(!bSuccess) { CloseHandle(process_info.hProcess); @@ -173,11 +165,17 @@ void Process::async_read() { } int Process::get_exit_code() { + if(id==0) + return -1; DWORD exit_code; - HANDLE process_info = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); - WaitForSingleObject(process_info, INFINITE); - GetExitCodeProcess(process_info, &exit_code); - CloseHandle(process_info); + HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); + if(process_handle) { + WaitForSingleObject(process_handle, INFINITE); + GetExitCodeProcess(process_handle, &exit_code); + CloseHandle(process_handle); + } + else + exit_code=-1; if(stdout_thread.joinable()) stdout_thread.join(); @@ -224,7 +222,10 @@ void Process::close_stdin() { stdin_mutex.unlock(); } -void Process::kill(process_id_type id, bool force) { +//Based on http://stackoverflow.com/a/1173396 +void Process::kill(id_type id, bool force) { + if(id==0) + return; HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if(snapshot) { PROCESSENTRY32 process; @@ -233,12 +234,12 @@ void Process::kill(process_id_type id, bool force) { if(Process32First(snapshot, &process)) { do { if(process.th32ParentProcessID==id) { - HANDLE process_info = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); - if(process_info) TerminateProcess(process_info, 2); + HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); + if(process_handle) TerminateProcess(process_handle, 2); } } while (Process32Next(snapshot, &process)); } } - HANDLE process_info = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); - if(process_info) TerminateProcess(process_info, 2); + HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id); + if(process_handle) TerminateProcess(process_handle, 2); } From 800ab3486c679636d001561bc8573a316a14ecbc Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 4 Dec 2015 19:35:38 +0100 Subject: [PATCH 8/9] A minor fix to process comments. --- src/process.h | 2 +- src/process_win.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/process.h b/src/process.h index 88b69ab..989f4f1 100644 --- a/src/process.h +++ b/src/process.h @@ -14,7 +14,7 @@ ///Create a new process given command and run path. ///Note: on Windows it seems impossible to specify which pipes to use. -///Thus, if read_stdout=nullptr, read_stderr=nullptr and open_std=false, +///Thus, if read_stdout=nullptr, read_stderr=nullptr and open_stdin=false, ///the stdout, stderr and stdin are sent to the parent process instead. ///Compile with -DMSYS_PROCESS_USE_SH to run command using "sh -c [command]" on Windows as well. class Process { diff --git a/src/process_win.cc b/src/process_win.cc index c2798ff..3be6472 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -7,7 +7,7 @@ using namespace std; //TODO: remove //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 read_stdout=nullptr, read_stderr=nullptr and open_std=false, the stdout, stderr and stdin are sent to the parent process instead. +//Thus, if read_stdout=nullptr, read_stderr=nullptr and open_stdin=false, the stdout, stderr and stdin are sent to the parent process instead. Process::id_type Process::open(const std::string &command, const std::string &path) { if(open_stdin) stdin_fd=std::unique_ptr(new fd_type); From e2c501db175f2d613dc6398bd3239dcc47d964f4 Mon Sep 17 00:00:00 2001 From: eidheim Date: Sat, 5 Dec 2015 11:31:38 +0100 Subject: [PATCH 9/9] Minor cleanup of process* --- src/process.cc | 4 ++++ src/process.h | 4 +++- src/process_unix.cc | 2 +- src/process_win.cc | 2 +- src/terminal.cc | 4 ++-- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/process.cc b/src/process.cc index 0ab07f0..f831ec9 100644 --- a/src/process.cc +++ b/src/process.cc @@ -19,3 +19,7 @@ Process::~Process() { if(stderr_thread.joinable()) stderr_thread.join(); } + +bool Process::write(const std::string &data) { + return write(data.c_str(), data.size()); +} diff --git a/src/process.h b/src/process.h index 989f4f1..f567664 100644 --- a/src/process.h +++ b/src/process.h @@ -38,7 +38,9 @@ public: ///Wait until process is finished, and return exit_code. int get_exit_code(); ///Write to stdin. - bool write_stdin(const char *bytes, size_t n); + bool write(const char *bytes, size_t n); + ///Write to stdin. Convenience function using write(const char *, size_t). + bool write(const std::string &data); ///Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. void close_stdin(); diff --git a/src/process_unix.cc b/src/process_unix.cc index 14e99ae..bdf9295 100644 --- a/src/process_unix.cc +++ b/src/process_unix.cc @@ -124,7 +124,7 @@ int Process::get_exit_code() { return exit_code; } -bool Process::write_stdin(const char *bytes, size_t n) { +bool Process::write(const char *bytes, size_t n) { stdin_mutex.lock(); if(stdin_fd) { if(::write(*stdin_fd, bytes, n)>=0) { diff --git a/src/process_win.cc b/src/process_win.cc index 3be6472..ea1c6bf 100644 --- a/src/process_win.cc +++ b/src/process_win.cc @@ -195,7 +195,7 @@ int Process::get_exit_code() { return static_cast(exit_code); } -bool Process::write_stdin(const char *bytes, size_t n) { +bool Process::write(const char *bytes, size_t n) { stdin_mutex.lock(); if(stdin_fd) { DWORD written; diff --git a/src/terminal.cc b/src/terminal.cc index b271279..539591f 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -115,7 +115,7 @@ int Terminal::process(std::istream &stdin_stream, std::ostream &stdout_stream, c auto read_n=stdin_stream.gcount(); if(read_n==0) break; - if(!process.write_stdin(buffer, read_n)) { + if(!process.write(buffer, read_n)) { break; } } @@ -265,7 +265,7 @@ bool Terminal::on_key_press_event(GdkEventKey *event) { } else if(event->keyval==GDK_KEY_Return) { stdin_buffer+='\n'; - processes.back()->write_stdin(stdin_buffer.c_str(), stdin_buffer.size()); + processes.back()->write(stdin_buffer); get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1)); stdin_buffer.clear(); }