Refactor config handling and related stuff
This commit is contained in:
parent
e7401a6bd1
commit
dfdb631b63
113
config.cpp
113
config.cpp
|
@ -1,27 +1,77 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
#include <memory>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
#include "config.h"
|
#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;
|
const char* path;
|
||||||
|
|
||||||
path = getenv("XDG_CONFIG_HOME");
|
path = getenv("XDG_CONFIG_HOME");
|
||||||
if (path) {
|
if (path) {
|
||||||
return std::string(path) + "/logmeow/config";
|
return std::string(path) + "/logmeow";
|
||||||
}
|
}
|
||||||
path = getenv("HOME");
|
path = getenv("HOME");
|
||||||
if (path) {
|
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");
|
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);
|
size_t line_capacity_size = 128 * sizeof(char);
|
||||||
char* line = static_cast<char*>(malloc(line_capacity_size));
|
char* line = static_cast<char*>(malloc(line_capacity_size));
|
||||||
if (line == nullptr) {
|
if (line == nullptr) {
|
||||||
|
@ -37,7 +87,7 @@ Config parse_config(FILE* file) {
|
||||||
if (errsv == ENOMEM) {
|
if (errsv == ENOMEM) {
|
||||||
throw std::bad_alloc();
|
throw std::bad_alloc();
|
||||||
} else if (errsv != 0) {
|
} 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 {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -50,8 +100,8 @@ Config parse_config(FILE* file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) {
|
if (strncmp(line, "logcat_command=", 15 * sizeof(char)) == 0) {
|
||||||
config.logcat_command = &line[16];
|
config.logcat_command = &line[15];
|
||||||
} else {
|
} else if (line[0] != '\0') {
|
||||||
std::invalid_argument e = std::invalid_argument(std::string("unknown config line: ") + line);
|
std::invalid_argument e = std::invalid_argument(std::string("unknown config line: ") + line);
|
||||||
free(line);
|
free(line);
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -61,11 +111,44 @@ Config parse_config(FILE* file) {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_config(std::ofstream& file, const Config& config) {
|
Config load_config() {
|
||||||
file << "# This is an auto-generated file, comments made will be lost\n"
|
std::string config_file_path = get_config_file_path();
|
||||||
<< "# This is a poor man's config file \"format\", there are only three legal lines:\n"
|
std::unique_ptr<FILE, decltype(&fclose_and_log)> config_file(fopen_or_raise(config_file_path.c_str(), "r", true), fclose_and_log);
|
||||||
<< "# # a comment, only available at the start of a line\n"
|
|
||||||
<< "# (an empty line, no whitespace)\n"
|
if (!config_file) {
|
||||||
<< "# key=value pairs, no spaces around the delimiter, and no unknown keys\n\n"
|
return Config();
|
||||||
<< "logcat_command=" << config.logcat_command;
|
}
|
||||||
|
|
||||||
|
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<FILE, decltype(&fclose_and_log)> 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()");
|
||||||
}
|
}
|
||||||
|
|
8
config.h
8
config.h
|
@ -1,12 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
std::string logcat_command;
|
std::string logcat_command;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string get_config_path();
|
std::string get_config_folder();
|
||||||
Config parse_config(FILE* file);
|
void create_config_folders_if_necessary();
|
||||||
void write_config(std::ofstream& file, const Config& config);
|
Config load_config();
|
||||||
|
void write_config(const Config& config);
|
||||||
|
|
|
@ -3,66 +3,76 @@
|
||||||
|
|
||||||
#include "config.h"
|
#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)) {
|
if (!ImGui::Begin("Settings", show_settings_window)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ImGui::Text("Modifying logcat command will take effect after closing this window and will restart logcat");
|
// TODO actually have process control
|
||||||
ImGui::InputTextWithHint("Logcat command", "adb logcat -Dv 'threadtime UTC epoch usec'", &inactive_config.logcat_command);
|
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();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void main_window(Config& active_config, Config& inactive_config, bool* show_demo_window,
|
static inline void main_window(Config& active_config, bool* show_demo_window,
|
||||||
bool* show_settings_window, bool* save_settings_when_settings_window_closed, bool* exit_requested_rev) {
|
bool* show_settings_window, bool* exit_requested_rev) {
|
||||||
if (!ImGui::Begin("LogMeow", exit_requested_rev)) {
|
if (!ImGui::Begin("LogMeow", exit_requested_rev)) {
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ImGui::Checkbox("Show Dear ImGui Demo Window", show_demo_window);
|
ImGui::Checkbox("Show Dear ImGui Demo Window", show_demo_window);
|
||||||
if (ImGui::Button("Settings")) {
|
if (ImGui::Button("Settings")) {
|
||||||
if (!*show_settings_window) {
|
|
||||||
inactive_config = active_config;
|
|
||||||
}
|
|
||||||
*show_settings_window = true;
|
*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::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void exit_modal_if_necessary(bool* run_event_loop) {
|
static inline void exit_modal_if_necessary(bool* run_event_loop) {
|
||||||
if (ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) {
|
if (!ImGui::BeginPopupModal("Exit?", nullptr, ImGuiWindowFlags_NoResize)) {
|
||||||
ImGui::Text("Are you sure you want to exit?");
|
return;
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
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_demo_window = false;
|
||||||
static bool show_settings_window = false;
|
static bool show_settings_window = false;
|
||||||
static bool save_settings_when_settings_window_closed = false;
|
|
||||||
static bool exit_requested_rev = true;
|
static bool exit_requested_rev = true;
|
||||||
static Config inactive_config;
|
|
||||||
|
|
||||||
if (show_demo_window) {
|
if (show_demo_window) {
|
||||||
ImGui::ShowDemoWindow(&show_demo_window);
|
ImGui::ShowDemoWindow(&show_demo_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show_settings_window) {
|
if (show_settings_window) {
|
||||||
settings_window(inactive_config, &show_settings_window);
|
settings_window(config, config_write_timer, &show_settings_window);
|
||||||
} else if (save_settings_when_settings_window_closed) {
|
}
|
||||||
active_config = inactive_config;
|
if (*config_write_timer > 0.0f) {
|
||||||
save_settings_when_settings_window_closed = false;
|
*config_write_timer -= ImGui::GetIO().DeltaTime;
|
||||||
|
if (*config_write_timer <= 0.0f) {
|
||||||
|
write_config_and_update_structures(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exit_requested_rev) {
|
if (!exit_requested_rev) {
|
||||||
|
@ -70,5 +80,5 @@ void event_loop(Config& active_config, bool* run_event_loop) {
|
||||||
exit_requested_rev = true;
|
exit_requested_rev = true;
|
||||||
}
|
}
|
||||||
exit_modal_if_necessary(run_event_loop);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
#include "config.h"
|
#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);
|
||||||
|
|
42
main.cpp
42
main.cpp
|
@ -17,23 +17,18 @@
|
||||||
int main(int, char**)
|
int main(int, char**)
|
||||||
{
|
{
|
||||||
Config config;
|
Config config;
|
||||||
FILE* config_file = nullptr;
|
|
||||||
try {
|
try {
|
||||||
config_file = fopen(get_config_path().c_str(), "r");
|
config = load_config();
|
||||||
if (config_file) {
|
|
||||||
config = parse_config(config_file);
|
|
||||||
} else if (errno != ENOENT) {
|
|
||||||
perror("Failed to open config file");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
} 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;
|
return 1;
|
||||||
}
|
}
|
||||||
if (config_file) {
|
|
||||||
if (fclose(config_file)) {
|
try {
|
||||||
perror("Failed to close config file");
|
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
|
// Setup SDL
|
||||||
|
@ -82,7 +77,10 @@ int main(int, char**)
|
||||||
// Setup Dear ImGui context
|
// Setup Dear ImGui context
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
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_NavEnableKeyboard; // Enable Keyboard Controls
|
||||||
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
|
||||||
|
|
||||||
|
@ -112,8 +110,9 @@ int main(int, char**)
|
||||||
|
|
||||||
// Main loop
|
// Main loop
|
||||||
bool run_event_loop = true;
|
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.)
|
// 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.
|
// 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.
|
// - 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_ImplSDL2_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
event_loop(config, &run_event_loop);
|
event_loop(config, &config_write_timer, &run_event_loop);
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
|
@ -154,5 +153,14 @@ int main(int, char**)
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
SDL_Quit();
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue