#include #include #include "log.h" #include "config.h" #include "logcat_thread.h" static std::string leftpad(std::string str, size_t characters) { if (str.size() < characters) { return str.insert(0, characters - str.size(), ' '); } return str; } static inline void write_config_and_update_structures(const Config& config) { try { write_config(config); } catch (const std::exception& e) { log(std::string("Failed to write config: ") + e.what()); return; } } static inline void check_for_logcat_items(LogcatThread& logcat_thread, std::vector& filtered_logcat_entries) { LogcatThreadItem* logcat_thread_item; while ((logcat_thread_item = logcat_thread.atomic_ring_buffer.get())) { if (std::holds_alternative(*logcat_thread_item)) { log_raw(std::move(std::get(*logcat_thread_item)), false); } else if (std::holds_alternative(*logcat_thread_item)) { filtered_logcat_entries.push_back(std::move(std::get(*logcat_thread_item))); } else { throw std::runtime_error("Cannot handle all possible logcat thread item variants"); } logcat_thread.atomic_ring_buffer.increment_read(); } } static inline void settings_window(Config& config, float* config_write_timer, bool* show_settings_window) { if (!ImGui::Begin("Settings", show_settings_window)) { ImGui::End(); return; } // TODO actually have process control ImGui::Text("Logcat command only takes effect when logcat is not running"); if (ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec uid'", &config.logcat_command)) { *config_write_timer = *config_write_timer > 0.0f ? *config_write_timer : 5.0f; } ImGui::Text("Font sizes only take effect when LogMeow is restarted"); #ifdef USE_FONTCONFIG if (ImGui::InputFloat("Normal font size", &config.normal_font_size, 0.5f, 1.0f, "%.3f")) { *config_write_timer = *config_write_timer > 0.0f ? *config_write_timer : 5.0f; } #endif if (ImGui::InputFloat("Monospace font size", &config.monospace_font_size, 0.5f, 1.0f, "%.3f")) { *config_write_timer = *config_write_timer > 0.0f ? *config_write_timer : 5.0f; } ImGui::End(); } static inline void logs_window(ImFont* monospace_font, bool* autoscrolling, bool* show_logs_window) { if (!ImGui::Begin("LogMeow Logs", show_logs_window)) { ImGui::End(); return; } if (ImGui::Button("Clear")) { log_entries.clear(); log_entry_line_offsets = {0}; } ImGui::SameLine(); if (ImGui::Button("Copy")) { ImGui::SetClipboardText(log_entries.c_str()); } ImGui::Separator(); // copied from imgui/imgui_demo.cpp: [SECTION] Example App: Debug Console / ShowExampleAppConsole() // and [SECTION] Example App: Long Text / ShowExampleAppLongText() // and [SECTION] Example App: Debug Log / ShowExampleAppLog() if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) { ImGui::PushFont(monospace_font); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGuiListClipper clipper; clipper.Begin(static_cast(log_entry_line_offsets.size())); while (clipper.Step()) { for (int i_u = clipper.DisplayStart; i_u < clipper.DisplayEnd; i_u++) { // what'd we do if we log the error about an error failing to show logs assert(i_u >= 0); size_t i = static_cast(i_u); const char* start_offset = &log_entries[log_entry_line_offsets[i]]; const char* end_offset = log_entry_line_offsets.size() > i + 1 ? &log_entries[log_entry_line_offsets[i + 1] - 1] : &log_entries[log_entries.size()]; ImGui::TextUnformatted(start_offset, end_offset); } } clipper.End(); ImGui::PopStyleVar(); ImGui::PopFont(); if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { *autoscrolling = true; ImGui::SetScrollHereY(1.0f); } } ImGui::EndChild(); ImGui::End(); } static inline void main_window(bool latest_log_entries_read, ImFont* monospace_font, std::vector& filtered_logcat_entries, bool* show_settings_window, bool* show_logs_window, bool* run_event_loop) { if (!ImGui::Begin("LogMeow", run_event_loop)) { ImGui::End(); return; } if (ImGui::Button("Settings")) { *show_settings_window = true; } ImGui::SameLine(); if (!latest_log_entries_read) { ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(0.0f, 0.6f, 0.6f)); 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)); } if (ImGui::Button("Logs")) { *show_logs_window = true; } if (!latest_log_entries_read) { ImGui::PopStyleColor(3); } ImGui::Separator(); // copied from imgui/imgui_demo.cpp: [SECTION] Example App: Debug Console / ShowExampleAppConsole() // and [SECTION] Example App: Long Text / ShowExampleAppLongText() // and [SECTION] Example App: Debug Log / ShowExampleAppLog() if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) { ImGui::PushFont(monospace_font); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGuiListClipper clipper; clipper.Begin(static_cast(filtered_logcat_entries.size())); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { LogcatEntry* logcat_entry = &filtered_logcat_entries[static_cast(i)]; char time_as_str[128] = {0}; strftime(time_as_str, 127 * sizeof(char), "%c", localtime(&logcat_entry->time)); std::string header = std::string(1, '[') + time_as_str + ' ' + leftpad(logcat_entry->user.value_or(" "), 5) + ' ' + leftpad(std::to_string(logcat_entry->pid), 5) + ' ' + leftpad(std::to_string(logcat_entry->tid), 5) + ' ' + leftpad(buffer_to(logcat_entry->buffer), 6) + ' ' + leftpad(priority_to(logcat_entry->priority), 7) + "] "; ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]); ImGui::TextUnformatted(header.data(), &header[header.size()]); ImGui::PopStyleColor(); std::string line = logcat_entry->tag + ": " + logcat_entry->message; ImGui::SameLine(); ImGui::TextUnformatted(line.data(), &line[line.size()]); } } clipper.End(); ImGui::PopStyleVar(); ImGui::PopFont(); if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { ImGui::SetScrollHereY(1.0f); } } ImGui::EndChild(); ImGui::End(); } #ifndef NDEBUG static inline void debug_window(LogcatThread& logcat_thread) { static bool show_demo_window = false; static size_t add_log_entry_presses = 1; static bool log_entry_every_second = false; static float log_entry_every_second_delta; if (show_demo_window) { ImGui::ShowDemoWindow(&show_demo_window); } if (!ImGui::Begin("LogMeow Debug")) { ImGui::End(); return; } ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::Checkbox("Show Dear ImGui Demo Window", &show_demo_window); ImGui::Separator(); if (ImGui::Button("Add Log Entry")) { log(std::string("Debug log entry #") + std::to_string(add_log_entry_presses++) + " (activated via manual button press)"); } ImGui::SameLine(); // returns true when it's pressed if (ImGui::Checkbox("Add log entry every second", &log_entry_every_second)) { log_entry_every_second_delta = 0.0f; } if (ImGui::Button("Add Log Entry with Newlines")) { log("The following should have five spaces: \"\n\n\n\n\n\""); } if (ImGui::Button("Test user assert")) { IM_ASSERT_USER_ERROR(0, "User assert tested"); } if (ImGui::Button("Request log entry from Logcat thread")) { logcat_thread.debug_log_request.test_and_set(); } if (log_entry_every_second) { log_entry_every_second_delta += ImGui::GetIO().DeltaTime; if (log_entry_every_second_delta >= 1.0f) { log_entry_every_second_delta = 0.0f; log(std::string("Debug log entry #") + std::to_string(add_log_entry_presses++) + " (activated by add log entry every second)"); } } ImGui::End(); } #endif void event_loop(ImFont* monospace_font, Config& config, float* config_write_timer, LogcatThread& logcat_thread, bool* run_event_loop) { static bool show_settings_window = false; static bool show_logs_window = false; static size_t log_entries_read = 0; static std::vector filtered_logcat_entries; check_for_logcat_items(logcat_thread, filtered_logcat_entries); #ifndef NDEBUG debug_window(logcat_thread); #endif if (show_settings_window) { settings_window(config, config_write_timer, &show_settings_window); } if (*config_write_timer > 0.0f) { *config_write_timer -= ImGui::GetIO().DeltaTime; if (*config_write_timer <= 0.0f) { write_config_and_update_structures(config); } } if (show_logs_window) { bool autoscrolling = false; logs_window(monospace_font, &autoscrolling, &show_logs_window); if (autoscrolling) { log_entries_read = log_entries.size(); } } // log_entries must not be mutated until the show logs button main_window(log_entries_read == log_entries.size(), monospace_font, filtered_logcat_entries, &show_settings_window, &show_logs_window, run_event_loop); }