Browse Source

Merge pull request #98 from eidheim/master

Shiny new process class and cleanup of terminal*
merge-requests/365/head
Jørgen Lien Sellæg 10 years ago
parent
commit
08ff48b79a
  1. 11
      src/CMakeLists.txt
  2. 2
      src/cmake.cc
  3. 2
      src/juci.cc
  4. 25
      src/process.cc
  5. 65
      src/process.h
  6. 159
      src/process_unix.cc
  7. 245
      src/process_win.cc
  8. 2
      src/source_clang.cc
  9. 275
      src/terminal.cc
  10. 20
      src/terminal.h
  11. 440
      src/terminal_unix.cc
  12. 512
      src/terminal_win.cc
  13. 16
      src/window.cc

11
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} -DMSYS_PROCESS_USE_SH -DJUCI_CMAKE_INSTALL_PREFIX=\\\"${CMAKE_INSTALL_PREFIX}\\\"")
endif()
INCLUDE(FindPkgConfig)
@ -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 dialogs_win.cc)
list(APPEND source_files process_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 terminal_unix.cc)
list(APPEND source_files process_unix.cc)
list(APPEND source_files dialogs_unix.cc)
endif()

2
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

2
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();
}

25
src/process.cc

@ -0,0 +1,25 @@
#include "process.h"
#include <iostream> //TODO: remove
using namespace std; //TODO: remove
Process::Process(const std::string &command, const std::string &path,
std::function<void(const char* bytes, size_t n)> read_stdout,
std::function<void(const char* bytes, size_t n)> read_stderr,
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();
}
Process::~Process() {
if(stdout_thread.joinable())
stdout_thread.join();
if(stderr_thread.joinable())
stderr_thread.join();
}
bool Process::write(const std::string &data) {
return write(data.c_str(), data.size());
}

65
src/process.h

@ -0,0 +1,65 @@
#ifndef JUCI_PROCESS_H_
#define JUCI_PROCESS_H_
#include <string>
#include <functional>
#include <vector>
#include <mutex>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/wait.h>
#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_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 {
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<void(const char *bytes, size_t n)> read_stdout=nullptr,
std::function<void(const char *bytes, size_t n)> read_stderr=nullptr,
bool open_stdin=false,
size_t buffer_size=131072);
~Process();
///Get the process id of the started process.
id_type get_id() {return id;}
///Wait until process is finished, and return exit_code.
int get_exit_code();
///Write to stdin.
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();
///Kill a given process id.
static void kill(id_type id, bool force=false);
private:
std::function<void(const char* bytes, size_t n)> read_stdout;
std::function<void(const char* bytes, size_t n)> read_stderr;
std::thread stdout_thread, stderr_thread;
bool open_stdin;
std::mutex stdin_mutex;
const size_t buffer_size;
std::unique_ptr<fd_type> stdout_fd, stderr_fd, stdin_fd;
id_type open(const std::string &command, const std::string &path);
id_type id;
void async_read();
};
#endif // JUCI_PROCESS_H_

159
src/process_unix.cc

@ -0,0 +1,159 @@
#include "process.h"
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <iostream> //TODO: remove
using namespace std; //TODO: remove
Process::id_type Process::open(const std::string &command, const std::string &path) {
if(open_stdin)
stdin_fd=std::unique_ptr<fd_type>(new fd_type);
if(read_stdout)
stdout_fd=std::unique_ptr<fd_type>(new fd_type);
if(read_stderr)
stderr_fd=std::unique_ptr<fd_type>(new fd_type);
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;
}
id_type 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<size_t>(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<size_t>(n));
});
}
}
int Process::get_exit_code() {
if(id<=0)
return -1;
int exit_code;
waitpid(id, &exit_code, 0);
if(stdout_thread.joinable())
stdout_thread.join();
if(stderr_thread.joinable())
stderr_thread.join();
close_stdin();
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::close_stdin() {
stdin_mutex.lock();
if(stdin_fd) {
close(*stdin_fd);
stdin_fd.reset();
}
stdin_mutex.unlock();
}
void Process::kill(id_type id, bool force) {
if(id<=0)
return;
if(force)
::kill(-id, SIGTERM);
else
::kill(-id, SIGINT);
}

245
src/process_win.cc

@ -0,0 +1,245 @@
#include "process.h"
#include <cstring>
#include "TlHelp32.h"
#include <iostream> //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 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<fd_type>(new fd_type);
if(read_stdout)
stdout_fd=std::unique_ptr<fd_type>(new fd_type);
if(read_stderr)
stderr_fd=std::unique_ptr<fd_type>(new fd_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 0;
if(!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) {
CloseHandle(g_hChildStd_IN_Rd);
CloseHandle(g_hChildStd_IN_Wr);
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 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 0;
}
}
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 0;
}
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 0;
}
}
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;
#ifdef MSYS_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, NULL, NULL, TRUE, 0,
NULL, path_ptr, &siStartInfo, &process_info);
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 0;
}
else {
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.dwProcessId;
}
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<CHAR*>(buffer), static_cast<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
read_stdout(buffer, static_cast<size_t>(n));
}
});
}
if(stderr_fd) {
stderr_thread=std::thread([this](){
DWORD n;
char buffer[buffer_size];
for (;;) {
BOOL bSuccess = ReadFile(*stderr_fd, static_cast<CHAR*>(buffer), static_cast<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
read_stderr(buffer, static_cast<size_t>(n));
}
});
}
}
int Process::get_exit_code() {
if(id==0)
return -1;
DWORD exit_code;
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();
if(stderr_thread.joinable())
stderr_thread.join();
close_stdin();
if(stdout_fd) {
CloseHandle(*stdout_fd);
stdout_fd.reset();
}
if(stderr_fd) {
CloseHandle(*stderr_fd);
stderr_fd.reset();
}
return static_cast<int>(exit_code);
}
bool Process::write(const char *bytes, size_t n) {
stdin_mutex.lock();
if(stdin_fd) {
DWORD written;
BOOL bSuccess=WriteFile(*stdin_fd, bytes, static_cast<DWORD>(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::close_stdin() {
stdin_mutex.lock();
if(stdin_fd) {
CloseHandle(*stdin_fd);
stdin_fd.reset();
}
stdin_mutex.unlock();
}
//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;
ZeroMemory(&process, sizeof(process));
process.dwSize = sizeof(process);
if(Process32First(snapshot, &process)) {
do {
if(process.th32ParentProcessID==id) {
HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID);
if(process_handle) TerminateProcess(process_handle, 2);
}
} while (Process32Next(snapshot, &process));
}
}
HANDLE process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, id);
if(process_handle) TerminateProcess(process_handle, 2);
}

2
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();

275
src/terminal.cc

@ -0,0 +1,275 @@
#include "terminal.h"
#include <iostream>
#include "logging.h"
#include "singletons.h"
#include "process.h"
#include <iostream> //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> process;
if(use_pipes)
process=std::unique_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);
}));
else
process=std::unique_ptr<Process>(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(std::string(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);
}, 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;
}
}
process.close_stdin();
return process.get_exit_code();
}
void Terminal::async_process(const std::string &command, const boost::filesystem::path &path, std::function<void(int exit_code)> callback) {
std::thread async_execute_thread([this, command, path, callback](){
processes_mutex.lock();
stdin_buffer.clear();
std::shared_ptr<Process> 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();
}
auto 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<size_t>(lines);
}
return static_cast<size_t>(get_buffer()->end().get_line())+deleted_lines;
}
void Terminal::print(size_t line_nr, const std::string &message){
if(line_nr<deleted_lines)
return;
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, "?");
}
auto end_line_iter=get_buffer()->get_iter_at_line(static_cast<int>(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::InProgress> Terminal::print_in_progress(std::string start_msg) {
std::shared_ptr<Terminal::InProgress> in_progress=std::shared_ptr<Terminal::InProgress>(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);
get_buffer()->insert_at_cursor(stdin_buffer.substr(stdin_buffer.size()-1));
stdin_buffer.clear();
}
}
processes_mutex.unlock();
return true;
}

20
src/terminal.h

@ -7,8 +7,8 @@
#include <boost/filesystem.hpp>
#include <thread>
#include <atomic>
#include <list>
#include <iostream>
#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<void(int exit_code)> 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<void(int exit_code)> 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<Gtk::TextTag> bold_tag;
std::mutex async_executes_mutex;
#ifdef _WIN32
std::list<std::pair<void*, void*> > async_executes;
#else
std::list<std::pair<pid_t, int> > async_executes;
#endif
std::vector<std::shared_ptr<Process> > processes;
std::mutex processes_mutex;
std::string stdin_buffer;
size_t deleted_lines=0;

440
src/terminal_unix.cc

@ -1,440 +0,0 @@
#include "terminal.h"
#include <iostream>
#include "logging.h"
#include "singletons.h"
#include <unistd.h>
#include <sys/wait.h>
#include <iostream> //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<n;c++)
message+=buffer[c];
async_print(message, true);
}
});
stderr_thread.detach();
std::thread stdout_thread([this, stdout_fd](){
char buffer[buffer_size];
ssize_t n;
while ((n=read(stdout_fd, buffer, buffer_size)) > 0) {
std::string message;
message.reserve(n);
for(ssize_t c=0;c<n;c++)
message+=buffer[c];
async_print(message);
}
});
stdout_thread.detach();
}
int exit_code;
waitpid(pid, &exit_code, 0);
if(use_pipes) {
close(stdin_fd);
close(stdout_fd);
close(stderr_fd);
}
return exit_code;
}
}
int Terminal::execute(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path) {
int stdin_fd, stdout_fd, stderr_fd;
auto pid=popen3(command, path.string(), &stdin_fd, &stdout_fd, &stderr_fd);
if (pid<=0) {
async_print("Error: Failed to run command: " + command + "\n", true);
return -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<n;c++)
message+=buffer[c];
async_print(message, true);
}
});
stderr_thread.detach();
std::thread stdout_thread([this, &stdout_stream, stdout_fd](){
char buffer[buffer_size];
ssize_t n;
while ((n=read(stdout_fd, buffer, buffer_size)) > 0) {
Glib::ustring umessage;
umessage.reserve(n);
for(ssize_t c=0;c<n;c++)
umessage+=buffer[c];
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);
}
});
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;
auto write_n=write(stdin_fd, buffer, read_n);
if(write_n==0)
break;
}
close(stdin_fd);
int exit_code;
waitpid(pid, &exit_code, 0);
close(stdout_fd);
close(stderr_fd);
return exit_code;
}
}
void Terminal::async_execute(const std::string &command, const boost::filesystem::path &path, std::function<void(int exit_code)> 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<n;c++)
message+=buffer[c];
async_print(message, true);
}
});
stderr_thread.detach();
std::thread stdout_thread([this, stdout_fd](){
char buffer[buffer_size];
ssize_t n;
while ((n=read(stdout_fd, buffer, buffer_size)) > 0) {
std::string message;
message.reserve(n);
for(ssize_t c=0;c<n;c++)
message+=buffer[c];
async_print(message);
}
});
stdout_thread.detach();
int exit_code;
waitpid(pid, &exit_code, 0);
async_executes_mutex.lock();
for(auto it=async_executes.begin();it!=async_executes.end();it++) {
if(it->first==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<size_t>(lines);
}
return static_cast<size_t>(get_buffer()->end().get_line())+deleted_lines;
}
void Terminal::print(size_t line_nr, const std::string &message){
if(line_nr<deleted_lines)
return;
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, "?");
}
auto end_line_iter=get_buffer()->get_iter_at_line(static_cast<int>(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::InProgress> Terminal::print_in_progress(std::string start_msg) {
std::shared_ptr<Terminal::InProgress> in_progress=std::shared_ptr<Terminal::InProgress>(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;
}

512
src/terminal_win.cc

@ -1,512 +0,0 @@
#include "terminal.h"
#include <iostream>
#include "logging.h"
#include "singletons.h"
#include <unistd.h>
#include <windows.h>
#include <iostream> //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<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
std::string message;
message.reserve(n);
for(DWORD c=0;c<n;c++)
message+=buffer[c];
async_print(message, true);
}
});
stderr_thread.detach();
std::thread stdout_thread([this, stdout_h](){
DWORD n;
CHAR buffer[buffer_size];
for (;;) {
BOOL bSuccess = ReadFile(stdout_h, buffer, static_cast<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
std::string message;
message.reserve(n);
for(DWORD c=0;c<n;c++)
message+=buffer[c];
async_print(message);
}
});
stdout_thread.detach();
}
unsigned long exit_code;
WaitForSingleObject(process, INFINITE);
GetExitCodeProcess(process, &exit_code);
CloseHandle(process);
if(use_pipes) {
CloseHandle(stdin_h);
CloseHandle(stdout_h);
CloseHandle(stderr_h);
}
return exit_code;
}
int Terminal::execute(std::istream &stdin_stream, std::ostream &stdout_stream, const std::string &command, const boost::filesystem::path &path) {
HANDLE stdin_h, stdout_h, stderr_h;
auto process=popen3(command, path.string(), &stdin_h, &stdout_h, &stderr_h);
if(process==NULL) {
async_print("Error: Failed to run command: " + command + "\n", true);
return -1;
}
std::thread stderr_thread([this, stderr_h](){
DWORD n;
CHAR buffer[buffer_size];
for (;;) {
BOOL bSuccess = ReadFile(stderr_h, buffer, static_cast<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
std::string message;
message.reserve(n);
for(DWORD c=0;c<n;c++)
message+=buffer[c];
async_print(message, true);
}
});
stderr_thread.detach();
std::thread stdout_thread([this, &stdout_stream, stdout_h](){
DWORD n;
CHAR buffer[buffer_size];
for (;;) {
BOOL bSuccess = ReadFile(stdout_h, buffer, static_cast<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
Glib::ustring umessage;
umessage.reserve(n);
for(DWORD c=0;c<n;c++)
umessage+=buffer[c];
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(), static_cast<ssize_t>(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<DWORD>(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<void(int exit_code)> 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<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
std::string message;
message.reserve(n);
for(DWORD c=0;c<n;c++)
message+=buffer[c];
async_print(message, true);
}
});
stderr_thread.detach();
std::thread stdout_thread([this, stdout_h](){
DWORD n;
CHAR buffer[buffer_size];
for (;;) {
BOOL bSuccess = ReadFile(stdout_h, buffer, static_cast<DWORD>(buffer_size), &n, NULL);
if(!bSuccess || n == 0)
break;
std::string message;
message.reserve(n);
for(DWORD c=0;c<n;c++)
message+=buffer[c];
async_print(message);
}
});
stdout_thread.detach();
unsigned long exit_code;
WaitForSingleObject(process, INFINITE);
GetExitCodeProcess(process, &exit_code);
async_executes_mutex.lock();
for(auto it=async_executes.begin();it!=async_executes.end();it++) {
if(it->first==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<size_t>(lines);
}
return static_cast<size_t>(get_buffer()->end().get_line())+deleted_lines;
}
void Terminal::print(size_t line_nr, const std::string &message){
if(line_nr<deleted_lines)
return;
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, "?");
}
auto end_line_iter=get_buffer()->get_iter_at_line(static_cast<int>(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::InProgress> Terminal::print_in_progress(std::string start_msg) {
std::shared_ptr<Terminal::InProgress> in_progress=std::shared_ptr<Terminal::InProgress>(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;
}

16
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;
}

Loading…
Cancel
Save