diff --git a/event_loop.cpp b/event_loop.cpp index db6d030..cf33097 100644 --- a/event_loop.cpp +++ b/event_loop.cpp @@ -70,7 +70,7 @@ void event_loop(ImFont* monospace_font, Config& active_config, LogcatThread& log } // log_entries must not be mutated until the show logs button - main_window(log_entries_read == log_entries.size(), monospace_font, + main_window(log_entries_read == log_entries.size(), monospace_font, logcat_thread, logcat_entries, filtered_logcat_entry_offsets, active_config, inactive_config, &show_settings_window, &show_filters_window, &show_exclusions_window, &show_logs_window, run_event_loop); diff --git a/logcat_thread.cpp b/logcat_thread.cpp index 07d033e..0f13f2e 100644 --- a/logcat_thread.cpp +++ b/logcat_thread.cpp @@ -1,6 +1,8 @@ #include #include +#include #include +#include #include #include @@ -72,7 +74,7 @@ static inline void handle_fd(int fd, char* buf, size_t* used, } -LogcatThread::LogcatThread() { +LogcatThread::LogcatThread(const std::string* logcat_command) : _logcat_command(logcat_command) { int fds[2]; struct epoll_event event = {.events = EPOLLIN | EPOLLET}; @@ -233,8 +235,141 @@ void LogcatThread::_run_epoll_round() { } } +void LogcatThread::_try_reap(bool stop_requested) { + int wstatus; + int res = waitpid(this->_logcat_pid, &wstatus, WNOHANG); + if (res == -1) { + try { + throw_system_error("waitpid()"); + } catch (const std::exception& e) { + LogEntry log_entry = {time(nullptr), e.what()}; + print_log(log_entry); + this->_put_if_not_stopped(std::move(log_entry)); + } + return; + } else if (res != this->_logcat_pid) { + return; + } + + this->_logcat_pid = -1; + this->_logcat_process_kill_attempts = 0; + // just in case if the process was terminated mid-write + this->_stdout_buf_used = 0; + this->_stderr_buf_used = 0; + this->logcat_process_running.clear(); + + if (WIFEXITED(wstatus)) { + if (WEXITSTATUS(wstatus) && !stop_requested) { + LogEntry log_entry = {time(nullptr), std::string("Logcat exited with ") + std::to_string(WEXITSTATUS(wstatus))}; + print_log(log_entry); + this->_put_if_not_stopped(std::move(log_entry)); + } + } else if (WIFSIGNALED(wstatus)) { + if (!stop_requested) { + LogEntry log_entry = {time(nullptr), std::string("Logcat exited with -") + std::to_string(WTERMSIG(wstatus))}; + print_log(log_entry); + this->_put_if_not_stopped(std::move(log_entry)); + } + } else { + LogEntry log_entry = {time(nullptr), std::string("Logcat disappeared (wstatus=") + std::to_string(wstatus) + ')'}; + print_log(log_entry); + this->_put_if_not_stopped(std::move(log_entry)); + } +} + +bool LogcatThread::_handle_stop_request() { + if (this->_logcat_pid == -1) { + return true; + } + + int signal = this->_logcat_process_kill_attempts++ < 3 ? SIGTERM : SIGKILL; + if (!kill(this->_logcat_pid, signal)) { + return false; + } + try { + throw_system_error("kill()"); + } catch (const std::exception& e) { + LogEntry log_entry = {time(nullptr), e.what()}; + print_log(log_entry); + this->_put_if_not_stopped(std::move(log_entry)); + } + return true; +} + +bool LogcatThread::_handle_start_request() { + if (this->_logcat_pid != -1) { + this->_handle_stop_request(); + return false; + } + auto dup2_or_die = [](int oldfd, int newfd) { + if (dup2(oldfd, newfd) == newfd) { + return; + } + try { + throw_system_error(std::string("dup2(") + std::to_string(oldfd) + ", " + std::to_string(newfd) + ')'); + } catch (const std::exception& e) { + print_log({time(nullptr), e.what()}); + } + exit(1); + }; + auto close_or_warn = [](int fd) { + if (!close(fd)) { + return; + } + try { + throw_system_error(std::string("close(") + std::to_string(fd) + ')'); + } catch (const std::exception& e) { + print_log({time(nullptr), e.what()}); + } + }; + + this->_logcat_pid = fork(); + if (this->_logcat_pid == -1) { + try { + throw_system_error("fork()"); + } catch (const std::exception& e) { + LogEntry log_entry = {time(nullptr), e.what()}; + print_log(log_entry); + this->_put_if_not_stopped(std::move(log_entry)); + } + return true; + } else if (this->_logcat_pid == 0) { + dup2_or_die(this->_stderr_write_fd, 2); + dup2_or_die(this->_stdout_write_fd, 1); + close_or_warn(this->_stdout_write_fd); + close_or_warn(this->_stderr_write_fd); + close_or_warn(this->_stdout_read_fd); + close_or_warn(this->_stderr_read_fd); + execlp("sh", "sh", "-c", this->_logcat_command->c_str(), nullptr); + try { + throw_system_error("execlp()"); + } catch (const std::exception& e) { + print_log({time(nullptr), e.what()}); + } + exit(1); + } else { + this->logcat_process_running.test_and_set(); + return true; + } +} + +bool LogcatThread::_run_process_round(LogcatProcessRequest request) { + bool task_done; + switch (request) { + case LogcatProcessRequest::None: task_done = false; break; + case LogcatProcessRequest::Start: task_done = this->_handle_start_request(); break; + case LogcatProcessRequest::Stop: task_done = this->_handle_stop_request(); break; + }; + + if (this->_logcat_pid != -1) { + this->_try_reap(request == LogcatProcessRequest::Stop); + } + + return task_done; +} + void LogcatThread::_run(std::stop_token stoken) { - while (!stoken.stop_requested()) { + while (!stoken.stop_requested() || this->_logcat_pid != -1) { #ifndef NDEBUG if (this->debug_log_request.test()) { LogEntry log_entry = {time(nullptr), "A log entry from the logcat thread :D"}; @@ -244,5 +379,11 @@ void LogcatThread::_run(std::stop_token stoken) { #endif this->_run_epoll_round(); + + if (stoken.stop_requested() && this->_logcat_pid != -1) { + this->_run_process_round(LogcatProcessRequest::Stop); + } else if (this->_run_process_round(this->logcat_process_request.load())) { + this->logcat_process_request.store(LogcatProcessRequest::None); + } } } diff --git a/logcat_thread.h b/logcat_thread.h index 98069a9..4636a32 100644 --- a/logcat_thread.h +++ b/logcat_thread.h @@ -8,18 +8,26 @@ typedef std::variant LogcatThreadItem; #define NEWLINE_BUF_SIZE 512 * 1024 +enum class LogcatProcessRequest { + None, + Start, + Stop, +}; + class LogcatThread { public: // https://stackoverflow.com/a/2173764 LogcatThread(const LogcatThread&) = delete; LogcatThread& operator=(const LogcatThread&) = delete; - LogcatThread(); + LogcatThread(const std::string* logcat_command); ~LogcatThread(); void request_stop(); void join(); AtomicRingBuffer atomic_ring_buffer; + std::atomic logcat_process_request = LogcatProcessRequest::None; + std::atomic_flag logcat_process_running; #ifndef NDEBUG std::atomic_flag debug_log_request; @@ -28,18 +36,29 @@ public: private: void _put_if_not_stopped(LogcatThreadItem item); void _handle_line(char* buf, size_t length, bool is_stdout); - void _run_epoll_round(); void _run(std::stop_token stoken); + void _run_epoll_round(); + + void _try_reap(bool stop_requested); + bool _handle_stop_request(); + bool _handle_start_request(); + bool _run_process_round(LogcatProcessRequest request); int _epoll_fd = -1; int _stdout_read_fd = -1; int _stdout_write_fd = -1; int _stderr_read_fd = -1; int _stderr_write_fd = -1; + char _stdout_buf[NEWLINE_BUF_SIZE]; size_t _stdout_buf_used = 0; char _stderr_buf[NEWLINE_BUF_SIZE]; size_t _stderr_buf_used = 0; + + pid_t _logcat_pid = -1; + char _logcat_process_kill_attempts = 0; + const std::string* _logcat_command; + Buffer _current_buffer = Buffer::Unknown; std::stop_source _stop_source; std::thread _thread; diff --git a/main.cpp b/main.cpp index e96550c..d31b687 100644 --- a/main.cpp +++ b/main.cpp @@ -134,7 +134,7 @@ int main(int, char**) { bool run_event_loop = true; LogcatThread logcat_thread = [&]() { try { - return LogcatThread(); + return LogcatThread(&config.logcat_command); } catch (const std::exception& e) { fprintf(stderr, "Failed to spawn logcat thread: %s\n", e.what()); exit(1); diff --git a/misc.cpp b/misc.cpp index 697fce1..38caae5 100644 --- a/misc.cpp +++ b/misc.cpp @@ -42,8 +42,21 @@ bool ImGui::RedButton(const char* label) { ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(0.0f, 0.7f, 0.7f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(0.0f, 0.8f, 0.8f)); - int res = ImGui::Button(label); + bool res = ImGui::Button(label); ImGui::PopStyleColor(3); return res; } + +bool ImGui::Button(const char* label, bool enabled) { + if (!enabled) { + ImGui::BeginDisabled(); + } + + bool res = ImGui::Button(label); + + if (!enabled) { + ImGui::EndDisabled(); + } + return res; +} diff --git a/misc.h b/misc.h index 46e1b8d..a6e3d08 100644 --- a/misc.h +++ b/misc.h @@ -10,5 +10,6 @@ void throw_system_error(std::string what); namespace ImGui { void TextUnformatted(const std::string& str); - bool RedButton(const char* text); + bool RedButton(const char* label); + bool Button(const char* label, bool enabled); }; // namespace ImGui diff --git a/windows/main.cpp b/windows/main.cpp index fcb8c1a..e3f6b79 100644 --- a/windows/main.cpp +++ b/windows/main.cpp @@ -5,6 +5,7 @@ #include "../misc.h" #include "../logcat_entry.h" +#include "../logcat_thread.h" #include "main.h" static inline void render_table(ImFont* monospace_font, std::vector& logcat_entries, std::vector& filtered_logcat_entry_offsets) { @@ -52,7 +53,7 @@ static inline void render_table(ImFont* monospace_font, std::vector ImGui::EndTable(); } -void main_window(bool latest_log_entries_read, ImFont* monospace_font, +void main_window(bool latest_log_entries_read, ImFont* monospace_font, LogcatThread& logcat_thread, std::vector& logcat_entries, std::vector& filtered_logcat_entry_offsets, const Config& active_config, Config& inactive_config, bool* show_settings_window, bool* show_filters_window, bool* show_exclusions_window, bool* show_logs_window, bool* run_event_loop) { @@ -71,19 +72,16 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, inactive_config.monospace_font_size = active_config.monospace_font_size; *show_settings_window = true; } - ImGui::SameLine(); if (ImGui::Button("Filters") && !*show_filters_window) { copy_filters(inactive_config.filters, active_config.filters); *show_filters_window = true; } - ImGui::SameLine(); if (ImGui::Button("Exclusions") && !*show_exclusions_window) { copy_filters(inactive_config.exclusions, active_config.exclusions); *show_exclusions_window = true; } - ImGui::SameLine(); bool open_logs; if (!latest_log_entries_read) { @@ -95,6 +93,32 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, *show_logs_window = true; } + bool can_send_logcat_request = logcat_thread.logcat_process_request.load() == LogcatProcessRequest::None; + bool logcat_running = logcat_thread.logcat_process_running.test(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Logcat:"); + ImGui::SameLine(); + + if (!can_send_logcat_request) { + ImGui::BeginDisabled(); + } + + if (ImGui::Button("Start", !logcat_running)) { + logcat_thread.logcat_process_request.store(LogcatProcessRequest::Start); + } + ImGui::SameLine(); + if (ImGui::Button("Stop", logcat_running)) { + logcat_thread.logcat_process_request.store(LogcatProcessRequest::Stop); + } + ImGui::SameLine(); + if (ImGui::Button("Restart", logcat_running)) { + logcat_thread.logcat_process_request.store(LogcatProcessRequest::Start); + } + + if (!can_send_logcat_request) { + ImGui::EndDisabled(); + } + ImGui::Separator(); // copied from imgui/imgui_demo.cpp: [SECTION] Example App: Debug Console / ShowExampleAppConsole() // and [SECTION] Example App: Long Text / ShowExampleAppLongText() diff --git a/windows/main.h b/windows/main.h index f5b5bd4..9f4d7bf 100644 --- a/windows/main.h +++ b/windows/main.h @@ -5,8 +5,9 @@ #include "../config.h" #include "../logcat_entry.h" +#include "../logcat_thread.h" -void main_window(bool latest_log_entries_read, ImFont* monospace_font, +void main_window(bool latest_log_entries_read, ImFont* monospace_font, LogcatThread& logcat_thread, std::vector& logcat_entries, std::vector& filtered_logcat_entry_offsets, const Config& active_config, Config& inactive_config, bool* show_settings_window, bool* show_filters_window, bool* show_exclusions_window, bool* show_logs_window, bool* run_event_loop); diff --git a/windows/settings.cpp b/windows/settings.cpp index d982914..199d2e0 100644 --- a/windows/settings.cpp +++ b/windows/settings.cpp @@ -17,7 +17,6 @@ void settings_window(Config& active_config, Config& inactive_config, bool* p_ope ImGui::End(); return; } - // TODO actually have process control ImGui::TextUnformatted("Logcat command only takes effect when logcat is not running"); ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec uid'", &inactive_config.logcat_command);