diff --git a/config.cpp b/config.cpp index 22a1207..3f40350 100644 --- a/config.cpp +++ b/config.cpp @@ -1,27 +1,77 @@ #include #include #include +#include #include -#include +#include +#include #include "config.h" -std::string get_config_path() { +static FILE* fopen_or_raise(const char* path, const char* mode, bool ignore_enoent) { + FILE* file = fopen(path, mode); + if (file || (ignore_enoent && errno == ENOENT)) { + return file; + } + throw std::system_error(errno, std::generic_category(), "fopen()"); +} + +static void fclose_and_log(FILE* file) { + if (!fclose(file)) { + return; + } + // TODO log() when logging exists +} + +static bool write(const std::string& ptr, FILE* file) { + return fwrite(ptr.data(), sizeof(char), ptr.size(), file) == ptr.size() * sizeof(char); +} +static bool write(const char* ptr, FILE* file) { + return fwrite(ptr, 1, strlen(ptr), file) == strlen(ptr); +} + + +std::string get_config_folder() { const char* path; path = getenv("XDG_CONFIG_HOME"); if (path) { - return std::string(path) + "/logmeow/config"; + return std::string(path) + "/logmeow"; } path = getenv("HOME"); if (path) { - return std::string(path) + "/.config/logmeow/config"; + return std::string(path) + "/.config/logmeow"; } throw std::runtime_error("cannot find suitable config folder, please set XDG_CONFIG_HOME or HOME"); } -Config parse_config(FILE* file) { +std::string get_config_file_path() { + return get_config_folder() + "/config"; +} + +// tries to create a directory with an empty string if it's just "/" but who cares +void create_config_folders_if_necessary() { + std::string path = get_config_folder(); + size_t pos = 0; + + while (pos != std::string::npos) { + pos = path.find('/', pos); + if (pos != std::string::npos) { + pos++; + } + if (!mkdir(path.substr(0, pos).c_str(), 0600)) { + continue; + } + if (errno == EEXIST) { + continue; + } + throw std::system_error(errno, std::generic_category(), "mkdir()"); + } +} + + +static inline Config load_config(FILE* file) { size_t line_capacity_size = 128 * sizeof(char); char* line = static_cast(malloc(line_capacity_size)); if (line == nullptr) { @@ -37,7 +87,7 @@ Config parse_config(FILE* file) { if (errsv == ENOMEM) { throw std::bad_alloc(); } else if (errsv != 0) { - throw std::system_error(errsv, std::generic_category(), "getline()"); + throw std::system_error(errsv, std::generic_category(), "reading config line"); } else { break; } @@ -50,8 +100,8 @@ Config parse_config(FILE* file) { } if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) { - config.logcat_command = &line[16]; - } else { + config.logcat_command = &line[15]; + } else if (line[0] != '\0') { std::invalid_argument e = std::invalid_argument(std::string("unknown config line: ") + line); free(line); throw e; @@ -61,11 +111,44 @@ Config parse_config(FILE* file) { return config; } -void write_config(std::ofstream& file, const Config& config) { - file << "# This is an auto-generated file, comments made will be lost\n" - << "# This is a poor man's config file \"format\", there are only three legal lines:\n" - << "# # a comment, only available at the start of a line\n" - << "# (an empty line, no whitespace)\n" - << "# key=value pairs, no spaces around the delimiter, and no unknown keys\n\n" - << "logcat_command=" << config.logcat_command; +Config load_config() { + std::string config_file_path = get_config_file_path(); + std::unique_ptr config_file(fopen_or_raise(config_file_path.c_str(), "r", true), fclose_and_log); + + if (!config_file) { + return Config(); + } + + return load_config(config_file.get()); +} + + +static inline void write_config(FILE* file, const Config& config) { + if (!write("# This is an auto-generated file, modifications will be lost\n" + "# This is a poor man's config file \"format\", there are only three legal lines:\n" + "# # a comment, only available at the start of a line\n" + "# (an empty line, no whitespace)\n" + "# key=value pairs, no spaces around the delimiter, and no unknown keys\n\n", file)) { + throw std::runtime_error("failed to write info comment"); + } + if (!write("logcat_command=", file)) { + throw std::runtime_error("failed to write logcat command key"); + } + if (!write(config.logcat_command, file)) { + throw std::runtime_error("failed to write logcat command value"); + } +} + +void write_config(const Config& config) { + std::string config_file_path = get_config_file_path(); + std::string tmp_config_file_path = config_file_path + ".tmp"; + + std::unique_ptr config_file(fopen_or_raise(tmp_config_file_path.c_str(), "w", false), fclose_and_log); + write_config(config_file.get(), config); + config_file.reset(); + + if (!rename(tmp_config_file_path.c_str(), config_file_path.c_str())) { + return; + } + throw std::system_error(errno, std::generic_category(), "rename()"); } diff --git a/config.h b/config.h index b2476a9..60de355 100644 --- a/config.h +++ b/config.h @@ -1,12 +1,12 @@ #pragma once #include -#include struct Config { std::string logcat_command; }; -std::string get_config_path(); -Config parse_config(FILE* file); -void write_config(std::ofstream& file, const Config& config); +std::string get_config_folder(); +void create_config_folders_if_necessary(); +Config load_config(); +void write_config(const Config& config); diff --git a/event_loop.cpp b/event_loop.cpp index 20b8fbd..ded68d6 100644 --- a/event_loop.cpp +++ b/event_loop.cpp @@ -3,66 +3,76 @@ #include "config.h" -static inline void settings_window(Config& inactive_config, bool* show_settings_window) { +static inline void write_config_and_update_structures(const Config& config) { + try { + write_config(config); + } catch (const std::exception& e) { + // TODO log() when logging exists + throw; + } +} + +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; } - ImGui::Text("Modifying logcat command will take effect after closing this window and will restart logcat"); - ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec'", &inactive_config.logcat_command); + // TODO actually have process control + ImGui::Text("Logcat command is only modifiable when logcat is stopped"); + if (ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec'", &config.logcat_command)) { + *config_write_timer = *config_write_timer > 0.0f ? *config_write_timer : 5.0f; + } ImGui::End(); } -static inline void main_window(Config& active_config, Config& inactive_config, bool* show_demo_window, - bool* show_settings_window, bool* save_settings_when_settings_window_closed, bool* exit_requested_rev) { +static inline void main_window(Config& active_config, bool* show_demo_window, + bool* show_settings_window, bool* exit_requested_rev) { if (!ImGui::Begin("LogMeow", exit_requested_rev)) { ImGui::End(); return; } ImGui::Checkbox("Show Dear ImGui Demo Window", show_demo_window); if (ImGui::Button("Settings")) { - if (!*show_settings_window) { - inactive_config = active_config; - } *show_settings_window = true; - *save_settings_when_settings_window_closed = true; } ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); ImGui::End(); } static inline void exit_modal_if_necessary(bool* run_event_loop) { - if (ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) { - ImGui::Text("Are you sure you want to exit?"); - - if (ImGui::Button("Yes", ImVec2(120, 0))) { - *run_event_loop = false; - } - ImGui::SameLine(); - if (ImGui::Button("No", ImVec2(120, 0))) { - ImGui::CloseCurrentPopup(); - } - ImGui::SetItemDefaultFocus(); - ImGui::EndPopup(); + if (!ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) { + return; } + ImGui::Text("Are you sure you want to exit?"); + + if (ImGui::Button("Yes", ImVec2(120, 0))) { + *run_event_loop = false; + } + ImGui::SameLine(); + if (ImGui::Button("No", ImVec2(120, 0))) { + ImGui::CloseCurrentPopup(); + } + ImGui::SetItemDefaultFocus(); + ImGui::EndPopup(); } -void event_loop(Config& active_config, bool* run_event_loop) { +void event_loop(Config& config, float* config_write_timer, bool* run_event_loop) { static bool show_demo_window = false; static bool show_settings_window = false; - static bool save_settings_when_settings_window_closed = false; static bool exit_requested_rev = true; - static Config inactive_config; if (show_demo_window) { ImGui::ShowDemoWindow(&show_demo_window); } if (show_settings_window) { - settings_window(inactive_config, &show_settings_window); - } else if (save_settings_when_settings_window_closed) { - active_config = inactive_config; - save_settings_when_settings_window_closed = false; + 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 (!exit_requested_rev) { @@ -70,5 +80,5 @@ void event_loop(Config& active_config, bool* run_event_loop) { exit_requested_rev = true; } exit_modal_if_necessary(run_event_loop); - main_window(active_config, inactive_config, &show_demo_window, &show_settings_window, &save_settings_when_settings_window_closed, &exit_requested_rev); + main_window(config, &show_demo_window, &show_settings_window, &exit_requested_rev); } diff --git a/event_loop.h b/event_loop.h index 855abde..57ef06f 100644 --- a/event_loop.h +++ b/event_loop.h @@ -2,4 +2,4 @@ #include "config.h" -void event_loop(Config& config, bool* run_event_loop); +void event_loop(Config& config, float* config_write_timer, bool* run_event_loop); diff --git a/main.cpp b/main.cpp index 67e69e1..900ab14 100644 --- a/main.cpp +++ b/main.cpp @@ -17,23 +17,18 @@ int main(int, char**) { Config config; - FILE* config_file = nullptr; try { - config_file = fopen(get_config_path().c_str(), "r"); - if (config_file) { - config = parse_config(config_file); - } else if (errno != ENOENT) { - perror("Failed to open config file"); - return 1; - } + config = load_config(); } catch (const std::exception& e) { - fprintf(stderr, "Failed to parse config: %s\n", e.what()); + fprintf(stderr, "Failed to load config: %s\n", e.what()); return 1; } - if (config_file) { - if (fclose(config_file)) { - perror("Failed to close config file"); - } + + try { + create_config_folders_if_necessary(); + } catch (const std::exception& e) { + fprintf(stderr, "Failed to create config folder: %s\n", e.what()); + return 1; } // Setup SDL @@ -82,7 +77,10 @@ int main(int, char**) // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; + ImGuiIO& io = ImGui::GetIO(); + // needs to be a variable to keep the c-style string around + std::string ini_file_path = get_config_folder() + "/imgui.ini"; + io.IniFilename = ini_file_path.c_str(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls @@ -112,8 +110,9 @@ int main(int, char**) // Main loop bool run_event_loop = true; - while (run_event_loop) - { + float config_write_timer = 0.0f; + + while (run_event_loop) { // Poll and handle events (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. @@ -134,7 +133,7 @@ int main(int, char**) ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); - event_loop(config, &run_event_loop); + event_loop(config, &config_write_timer, &run_event_loop); // Rendering ImGui::Render(); @@ -154,5 +153,14 @@ int main(int, char**) SDL_DestroyWindow(window); SDL_Quit(); + if (config_write_timer > 0.0f) { + try { + write_config(config); + } catch (const std::exception& e) { + fprintf(stderr, "Failed to write config: %s\n", e.what()); + return 1; + } + } + return 0; }