Compare commits
4 Commits
283b5e9f4d
...
72591390d6
Author | SHA1 | Date |
---|---|---|
blankie | 72591390d6 | |
blankie | d4e6d72a18 | |
blankie | 46389de61f | |
blankie | 39a7faa465 |
|
@ -9,17 +9,17 @@ project(logmeow CXX)
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
find_package(Freetype 2 REQUIRED)
|
find_package(Freetype 2 REQUIRED)
|
||||||
find_package(nlohmann_json REQUIRED)
|
find_package(nlohmann_json REQUIRED)
|
||||||
list(APPEND LIBS -lpcre2-8 -lpcre2-posix SDL2 nlohmann_json::nlohmann_json imgui)
|
list(APPEND LIBS -lpcre2-8 -lpcre2-posix SDL2 nlohmann_json::nlohmann_json imgui nfd)
|
||||||
list(APPEND IMGUI_LIBS SDL2 Freetype::Freetype)
|
list(APPEND IMGUI_LIBS SDL2 Freetype::Freetype)
|
||||||
|
|
||||||
set(INCLUDES "")
|
set(INCLUDES "")
|
||||||
set(SOURCES "")
|
set(SOURCES "")
|
||||||
set(IMGUI_SOURCES "")
|
set(IMGUI_SOURCES "")
|
||||||
set(DEFINITIONS "")
|
set(DEFINITIONS "")
|
||||||
# imgui include because <GH ISSUE LINK HERE>
|
# imgui include because https://github.com/ocornut/imgui/issues/6184#issuecomment-1439570929
|
||||||
list(APPEND INCLUDES thirdparty thirdparty/imgui /usr/include/SDL2)
|
list(APPEND INCLUDES thirdparty thirdparty/imgui /usr/include/SDL2)
|
||||||
list(APPEND SOURCES main.cpp event_loop.cpp logcat_thread.cpp logcat_entry.cpp log.cpp config.cpp filters.cpp misc.cpp pcre2_wrapper.cpp
|
list(APPEND SOURCES main.cpp event_loop.cpp logcat_thread.cpp logcat_entry.cpp log.cpp config.cpp filters.cpp misc.cpp pcre2_wrapper.cpp
|
||||||
group_panel.cpp fragments/filters.cpp windows/logs.cpp windows/settings.cpp windows/main.cpp)
|
group_panel.cpp fragments/filters.cpp fragments/export.cpp windows/logs.cpp windows/settings.cpp windows/main.cpp)
|
||||||
list(APPEND IMGUI_SOURCES thirdparty/imgui/imgui.cpp thirdparty/imgui/imgui_draw.cpp thirdparty/imgui/imgui_widgets.cpp thirdparty/imgui/imgui_tables.cpp
|
list(APPEND IMGUI_SOURCES thirdparty/imgui/imgui.cpp thirdparty/imgui/imgui_draw.cpp thirdparty/imgui/imgui_widgets.cpp thirdparty/imgui/imgui_tables.cpp
|
||||||
thirdparty/imgui/misc/cpp/imgui_stdlib.cpp thirdparty/imgui/misc/freetype/imgui_freetype.cpp
|
thirdparty/imgui/misc/cpp/imgui_stdlib.cpp thirdparty/imgui/misc/freetype/imgui_freetype.cpp
|
||||||
thirdparty/imgui/backends/imgui_impl_sdl2.cpp thirdparty/imgui/backends/imgui_impl_opengl3.cpp)
|
thirdparty/imgui/backends/imgui_impl_sdl2.cpp thirdparty/imgui/backends/imgui_impl_opengl3.cpp)
|
||||||
|
@ -88,6 +88,9 @@ target_compile_definitions(imgui PRIVATE ${DEFINITIONS})
|
||||||
# it seems like compilers may issue a warning for unknown warnings to ignore
|
# it seems like compilers may issue a warning for unknown warnings to ignore
|
||||||
target_compile_options(imgui PRIVATE ${FLAGS} -Wno-unknown-warning-option -Wno-conversion)
|
target_compile_options(imgui PRIVATE ${FLAGS} -Wno-unknown-warning-option -Wno-conversion)
|
||||||
|
|
||||||
|
set(NFD_PORTAL ON)
|
||||||
|
add_subdirectory(thirdparty/nativefiledialog-extended)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${SOURCES})
|
add_executable(${PROJECT_NAME} ${SOURCES})
|
||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
|
|
37
config.cpp
37
config.cpp
|
@ -1,29 +1,13 @@
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "file.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
static FILE* fopen_or_raise(const char* __restrict path, const char* __restrict mode, bool ignore_enoent) {
|
|
||||||
FILE* file = fopen(path, mode);
|
|
||||||
if (!file && !(ignore_enoent && errno == ENOENT)) {
|
|
||||||
throw_system_error(std::string("fopen(") + quote(path) + ')');
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fclose_and_log(FILE* file) {
|
|
||||||
if (!fclose(file)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log(std::string("Failed to close a file: fclose(): ") + strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::string get_config_folder() {
|
std::string get_config_folder() {
|
||||||
const char* path;
|
const char* path;
|
||||||
|
|
||||||
|
@ -76,13 +60,15 @@ void from_json(const nlohmann::json& j, Config& config) {
|
||||||
|
|
||||||
Config load_config() {
|
Config load_config() {
|
||||||
std::string config_file_path = get_config_file_path();
|
std::string config_file_path = get_config_file_path();
|
||||||
std::unique_ptr<FILE, decltype(&fclose_and_log)> config_file(fopen_or_raise(config_file_path.c_str(), "r", true), fclose_and_log);
|
try {
|
||||||
|
File config_file(config_file_path.c_str(), "r");
|
||||||
if (!config_file) {
|
return nlohmann::json::parse(config_file.get());
|
||||||
|
} catch (const std::system_error& e) {
|
||||||
|
if (e.code().value() != ENOENT) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
return Config();
|
return Config();
|
||||||
}
|
}
|
||||||
|
|
||||||
return nlohmann::json::parse(config_file.get()).get<Config>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -98,10 +84,11 @@ void write_config(const Config& config) {
|
||||||
std::string config_file_path = get_config_file_path();
|
std::string config_file_path = get_config_file_path();
|
||||||
std::string tmp_config_file_path = config_file_path + ".tmp";
|
std::string tmp_config_file_path = config_file_path + ".tmp";
|
||||||
|
|
||||||
std::unique_ptr<FILE, decltype(&fclose_and_log)> config_file(fopen_or_raise(tmp_config_file_path.c_str(), "w", false), fclose_and_log);
|
{
|
||||||
|
File config_file(tmp_config_file_path.c_str(), "w");
|
||||||
std::string str_config = nlohmann::json(config).dump(4);
|
std::string str_config = nlohmann::json(config).dump(4);
|
||||||
fwrite(str_config.data(), sizeof(char), str_config.size(), config_file.get());
|
config_file.write(std::move(str_config));
|
||||||
config_file.reset();
|
}
|
||||||
|
|
||||||
if (!rename(tmp_config_file_path.c_str(), config_file_path.c_str())) {
|
if (!rename(tmp_config_file_path.c_str(), config_file_path.c_str())) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -17,10 +17,10 @@ static inline void check_for_logcat_items(LogcatThread& logcat_thread, const Con
|
||||||
LogcatThreadItem* logcat_thread_item;
|
LogcatThreadItem* logcat_thread_item;
|
||||||
|
|
||||||
while ((logcat_thread_item = logcat_thread.atomic_ring_buffer.get())) {
|
while ((logcat_thread_item = logcat_thread.atomic_ring_buffer.get())) {
|
||||||
if (std::holds_alternative<LogEntry>(*logcat_thread_item)) {
|
if (LogEntry* log_entry = std::get_if<LogEntry>(logcat_thread_item)) {
|
||||||
log(std::move(std::get<LogEntry>(*logcat_thread_item)), false);
|
log(std::move(*log_entry), false);
|
||||||
} else if (std::holds_alternative<LogcatEntry>(*logcat_thread_item)) {
|
} else if (LogcatEntry* logcat_entry = std::get_if<LogcatEntry>(logcat_thread_item)) {
|
||||||
logcat_entries.push_back(std::move(std::get<LogcatEntry>(*logcat_thread_item)));
|
logcat_entries.push_back(std::move(*logcat_entry));
|
||||||
if (matches(logcat_entries.back(), active_config.filters, active_config.exclusions)) {
|
if (matches(logcat_entries.back(), active_config.filters, active_config.exclusions)) {
|
||||||
filtered_logcat_entry_offsets.push_back(logcat_entries.size() - 1);
|
filtered_logcat_entry_offsets.push_back(logcat_entries.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "../log.h"
|
||||||
|
#include "../misc.h"
|
||||||
|
|
||||||
|
static void try_close(FILE* file);
|
||||||
|
|
||||||
|
// Not usable for logcat thread!
|
||||||
|
class File {
|
||||||
|
public:
|
||||||
|
// https://stackoverflow.com/a/2173764
|
||||||
|
File(const File&) = delete;
|
||||||
|
File& operator=(const File&) = delete;
|
||||||
|
|
||||||
|
inline constexpr File(File&& other) {
|
||||||
|
try_close(this->_file);
|
||||||
|
this->_file = other._file;
|
||||||
|
other._file = nullptr;
|
||||||
|
}
|
||||||
|
inline constexpr File& operator=(File&& other) {
|
||||||
|
if (this == &other) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
try_close(this->_file);
|
||||||
|
this->_file = other._file;
|
||||||
|
other._file = nullptr;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
File(const char* __restrict path, const char* __restrict mode) {
|
||||||
|
this->_file = fopen(path, mode);
|
||||||
|
if (!this->_file) {
|
||||||
|
throw_system_error(std::string("fopen(") + quote(path) + ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~File() {
|
||||||
|
try_close(this->_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const char* data, size_t length) {
|
||||||
|
if (fwrite(data, 1, length, this->_file) != length) {
|
||||||
|
throw_system_error("fwrite()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline constexpr void write(const std::string& str) {
|
||||||
|
this->write(str.data(), str.size());
|
||||||
|
}
|
||||||
|
inline constexpr FILE* get() const noexcept {
|
||||||
|
return this->_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FILE* _file = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void try_close(FILE* file) {
|
||||||
|
if (file && fclose(file)) {
|
||||||
|
log(std::string("Failed to close a file: fclose(): ") + strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
#include <imgui/imgui.h>
|
||||||
|
#include <nativefiledialog-extended/src/include/nfd.h>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "../file.h"
|
||||||
|
#include "../logcat_entry.h"
|
||||||
|
#include "export.h"
|
||||||
|
|
||||||
|
enum class ExportAmount : int {
|
||||||
|
All = 0,
|
||||||
|
Filtered = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void export_logcat_entries(File& file, const std::vector<LogcatEntry>& logcat_entries,
|
||||||
|
const std::vector<size_t>& filtered_logcat_entry_offsets, ExportAmount export_amount, bool export_buffers);
|
||||||
|
static inline std::optional<File> save_file_picker();
|
||||||
|
|
||||||
|
void export_fragment(const std::vector<LogcatEntry>& logcat_entries, const std::vector<size_t>& filtered_logcat_entry_offsets) {
|
||||||
|
static ExportAmount export_amount = ExportAmount::Filtered;
|
||||||
|
static bool export_buffers = true;
|
||||||
|
|
||||||
|
ImGui::RadioButton("Export all entries", reinterpret_cast<int*>(&export_amount), static_cast<int>(ExportAmount::All));
|
||||||
|
ImGui::RadioButton("Export filtered entries", reinterpret_cast<int*>(&export_amount), static_cast<int>(ExportAmount::Filtered));
|
||||||
|
ImGui::Checkbox("Export buffers", &export_buffers);
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::Button("Export to File")) {
|
||||||
|
std::optional<File> file = save_file_picker();
|
||||||
|
if (file) {
|
||||||
|
export_logcat_entries(*file, logcat_entries, filtered_logcat_entry_offsets, export_amount, export_buffers);
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Export to Clipboard")) {
|
||||||
|
// TODO exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void export_logcat_entries(File& file, const std::vector<LogcatEntry>& logcat_entries,
|
||||||
|
const std::vector<size_t>& filtered_logcat_entry_offsets, ExportAmount export_amount, bool export_buffers) {
|
||||||
|
// TODO actually do the job
|
||||||
|
file.write(std::string("Count of all entries: ") + std::to_string(logcat_entries.size()) + '\n');
|
||||||
|
file.write(std::string("Count of filtered entries: ") + std::to_string(filtered_logcat_entry_offsets.size()) + '\n');
|
||||||
|
file.write(std::string("Export amount: ") + (export_amount == ExportAmount::All ? "all" : "filtered") + '\n');
|
||||||
|
file.write(std::string("Export buffers: ") + (export_buffers ? "true" : "false") + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::optional<File> save_file_picker() {
|
||||||
|
nfdchar_t* path;
|
||||||
|
nfdfilteritem_t filters[1] = {{"Log file", "log"}};
|
||||||
|
nfdresult_t res = NFD_SaveDialog(&path, filters, 1, nullptr, "logcat.log");
|
||||||
|
|
||||||
|
if (res == NFD_OKAY) {
|
||||||
|
try {
|
||||||
|
File file(path, "w");
|
||||||
|
NFD_FreePath(path);
|
||||||
|
return std::move(file);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
NFD_FreePath(path);
|
||||||
|
log(std::string("Failed to open file from file picker: ") + e.what());
|
||||||
|
}
|
||||||
|
} else if (res == NFD_CANCEL) {
|
||||||
|
// dialog was canceled, shrug
|
||||||
|
} else {
|
||||||
|
log(std::string("Failed to open file picker: ") + NFD_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct LogcatEntry; // forward declaration from ../logcat_entry.h
|
||||||
|
|
||||||
|
void export_fragment(const std::vector<LogcatEntry>& logcat_entries, const std::vector<size_t>& filtered_logcat_entry_offsets);
|
10
main.cpp
10
main.cpp
|
@ -12,6 +12,7 @@
|
||||||
#else
|
#else
|
||||||
#include <SDL_opengl.h>
|
#include <SDL_opengl.h>
|
||||||
#endif
|
#endif
|
||||||
|
#include <nativefiledialog-extended/src/include/nfd.h>
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "fonts.h"
|
#include "fonts.h"
|
||||||
|
@ -47,6 +48,13 @@ int main() {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup NFDe
|
||||||
|
if (NFD_Init() != NFD_OKAY) {
|
||||||
|
SDL_Quit();
|
||||||
|
fprintf(stderr, "NFD_Init(): %s\n", NFD_GetError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Decide GL+GLSL versions
|
// Decide GL+GLSL versions
|
||||||
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
#if defined(IMGUI_IMPL_OPENGL_ES2)
|
||||||
// GL ES 2.0 + GLSL 100
|
// GL ES 2.0 + GLSL 100
|
||||||
|
@ -178,6 +186,8 @@ int main() {
|
||||||
ImGui_ImplSDL2_Shutdown();
|
ImGui_ImplSDL2_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
|
NFD_Quit();
|
||||||
|
|
||||||
SDL_GL_DeleteContext(gl_context);
|
SDL_GL_DeleteContext(gl_context);
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit c9a53aa74d84cdb6471651f7b3da14d7248d58c7
|
Subproject commit f65bcf481ab34cd07d3909aab1479f409fa79f2f
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 7909f55d912fe353ee2df6b08eec9313b50685f7
|
|
@ -1,4 +1,5 @@
|
||||||
#include <imgui/imgui.h>
|
#include <imgui/imgui.h>
|
||||||
|
#include <imgui/imgui_internal.h>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -7,6 +8,7 @@
|
||||||
#include "../config.h"
|
#include "../config.h"
|
||||||
#include "../logcat_entry.h"
|
#include "../logcat_entry.h"
|
||||||
#include "../logcat_thread.h"
|
#include "../logcat_thread.h"
|
||||||
|
#include "../fragments/export.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
static inline void render_table_item_context_menu(const LogcatEntry& logcat_entry) {
|
static inline void render_table_item_context_menu(const LogcatEntry& logcat_entry) {
|
||||||
|
@ -166,6 +168,17 @@ void main_window(bool latest_log_entries_read, ImFont* monospace_font, LogcatThr
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Export")) {
|
||||||
|
ImGui::OpenPopup("export_logcat");
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("export_logcat")) {
|
||||||
|
export_fragment(logcat_entries, filtered_logcat_entry_offsets);
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
// copied from imgui/imgui_demo.cpp: [SECTION] Example App: Debug Console / ShowExampleAppConsole()
|
// copied from imgui/imgui_demo.cpp: [SECTION] Example App: Debug Console / ShowExampleAppConsole()
|
||||||
// and [SECTION] Example App: Long Text / ShowExampleAppLongText()
|
// and [SECTION] Example App: Long Text / ShowExampleAppLongText()
|
||||||
|
|
Loading…
Reference in New Issue