diff options
author | dec05eba <dec05eba@protonmail.com> | 2024-08-06 05:57:21 +0200 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2024-08-06 05:57:21 +0200 |
commit | 9f1fddc47ce10fbc65cdeaa70461063b9921434e (patch) | |
tree | e9a8f19fa2ea5445e62ac2f4f8a488c591805347 /src | |
parent | b778fd7cc654f28a2bfe0ff74537f120241b289c (diff) |
Copy Config from gpu-screen-recorder-gtk, make it more modern and efficient with string_view and variant, use string_view in gsr info parsing
Diffstat (limited to 'src')
-rw-r--r-- | src/Config.cpp | 153 | ||||
-rw-r--r-- | src/GsrInfo.cpp | 126 | ||||
-rw-r--r-- | src/Utils.cpp | 176 | ||||
-rw-r--r-- | src/gui/Button.cpp | 6 | ||||
-rw-r--r-- | src/main.cpp | 2 |
5 files changed, 387 insertions, 76 deletions
diff --git a/src/Config.cpp b/src/Config.cpp new file mode 100644 index 0000000..51f14f1 --- /dev/null +++ b/src/Config.cpp @@ -0,0 +1,153 @@ +#include "../include/Config.hpp" +#include <variant> +#include <limits.h> +#include <inttypes.h> +#include <libgen.h> + +namespace gsr { + #define FORMAT_I32 "%" PRIi32 + #define FORMAT_I64 "%" PRIi64 + #define FORMAT_U32 "%" PRIu32 + + using ConfigValue = std::variant<bool*, std::string*, int32_t*, ConfigHotkey*, std::vector<std::string>*>; + + static std::map<std::string_view, ConfigValue> get_config_options(Config &config) { + return { + {"main.record_area_option", &config.main_config.record_area_option}, + {"main.record_area_width", &config.main_config.record_area_width}, + {"main.record_area_height", &config.main_config.record_area_height}, + {"main.fps", &config.main_config.fps}, + {"main.merge_audio_tracks", &config.main_config.merge_audio_tracks}, + {"main.audio_input", &config.main_config.audio_input}, + {"main.color_range", &config.main_config.color_range}, + {"main.quality", &config.main_config.quality}, + {"main.codec", &config.main_config.video_codec}, + {"main.audio_codec", &config.main_config.audio_codec}, + {"main.framerate_mode", &config.main_config.framerate_mode}, + {"main.advanced_view", &config.main_config.advanced_view}, + {"main.overclock", &config.main_config.overclock}, + {"main.show_recording_started_notifications", &config.main_config.show_recording_started_notifications}, + {"main.show_recording_stopped_notifications", &config.main_config.show_recording_stopped_notifications}, + {"main.show_recording_saved_notifications", &config.main_config.show_recording_saved_notifications}, + {"main.record_cursor", &config.main_config.record_cursor}, + {"main.hide_window_when_recording", &config.main_config.hide_window_when_recording}, + {"main.software_encoding_warning_shown", &config.main_config.software_encoding_warning_shown}, + {"main.restore_portal_session", &config.main_config.restore_portal_session}, + + {"streaming.service", &config.streaming_config.streaming_service}, + {"streaming.youtube.key", &config.streaming_config.youtube.stream_key}, + {"streaming.twitch.key", &config.streaming_config.twitch.stream_key}, + {"streaming.custom.url", &config.streaming_config.custom.url}, + {"streaming.custom.container", &config.streaming_config.custom.container}, + {"streaming.start_stop_recording_hotkey", &config.streaming_config.start_stop_recording_hotkey}, + + {"record.save_directory", &config.record_config.save_directory}, + {"record.container", &config.record_config.container}, + {"record.start_stop_recording_hotkey", &config.record_config.start_stop_recording_hotkey}, + {"record.pause_unpause_recording_hotkey", &config.record_config.pause_unpause_recording_hotkey}, + + {"replay.save_directory", &config.replay_config.save_directory}, + {"replay.container", &config.replay_config.container}, + {"replay.time", &config.replay_config.replay_time}, + {"replay.start_stop_recording_hotkey", &config.replay_config.start_stop_recording_hotkey}, + {"replay.save_recording_hotkey", &config.replay_config.save_recording_hotkey} + }; + } + + Config read_config(bool &config_empty) { + Config config; + + const std::string config_path = get_config_dir() + "/config"; + std::string file_content; + if(!file_get_content(config_path.c_str(), file_content)) { + fprintf(stderr, "Warning: Failed to read config file: %s\n", config_path.c_str()); + config_empty = true; + return config; + } + + auto config_options = get_config_options(config); + + string_split_char(file_content, '\n', [&](std::string_view line) { + const std::optional<KeyValue> key_value = parse_key_value(line); + if(!key_value) { + fprintf(stderr, "Warning: Invalid config option format: %.*s\n", (int)line.size(), line.data()); + return true; + } + + if(key_value->key.empty() || key_value->value.empty()) + return true; + + auto it = config_options.find(key_value->key); + if(it == config_options.end()) + return true; + + if(std::holds_alternative<bool*>(it->second)) { + *std::get<bool*>(it->second) = key_value->value == "true"; + } else if(std::holds_alternative<std::string*>(it->second)) { + std::get<std::string*>(it->second)->assign(key_value->value.data(), key_value->value.size()); + } else if(std::holds_alternative<int32_t*>(it->second)) { + std::string value_str(key_value->value); + int32_t *value = std::get<int32_t*>(it->second); + if(sscanf(value_str.c_str(), FORMAT_I32, value) != 1) { + fprintf(stderr, "Warning: Invalid config option value for %.*s\n", (int)key_value->key.size(), key_value->key.data()); + *value = 0; + } + } else if(std::holds_alternative<ConfigHotkey*>(it->second)) { + std::string value_str(key_value->value); + ConfigHotkey *config_hotkey = std::get<ConfigHotkey*>(it->second); + if(sscanf(value_str.c_str(), FORMAT_I64 " " FORMAT_U32, &config_hotkey->keysym, &config_hotkey->modifiers) != 2) { + fprintf(stderr, "Warning: Invalid config option value for %.*s\n", (int)key_value->key.size(), key_value->key.data()); + config_hotkey->keysym = 0; + config_hotkey->modifiers = 0; + } + } else if(std::holds_alternative<ConfigHotkey*>(it->second)) { + std::string array_value(key_value->value); + std::get<std::vector<std::string>*>(it->second)->push_back(std::move(array_value)); + } + + return true; + }); + + return config; + } + + void save_config(Config &config) { + const std::string config_path = get_config_dir() + "/config"; + + char dir_tmp[PATH_MAX]; + snprintf(dir_tmp, sizeof(dir_tmp), "%s", config_path.c_str()); + char *dir = dirname(dir_tmp); + + if(create_directory_recursive(dir) != 0) { + fprintf(stderr, "Warning: Failed to create config directory: %s\n", dir); + return; + } + + FILE *file = fopen(config_path.c_str(), "wb"); + if(!file) { + fprintf(stderr, "Warning: Failed to create config file: %s\n", config_path.c_str()); + return; + } + + const auto config_options = get_config_options(config); + for(auto it : config_options) { + if(std::holds_alternative<bool*>(it.second)) { + fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), *std::get<bool*>(it.second) ? "true" : "false"); + } else if(std::holds_alternative<std::string*>(it.second)) { + fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), std::get<std::string*>(it.second)->c_str()); + } else if(std::holds_alternative<int32_t*>(it.second)) { + fprintf(file, "%.*s " FORMAT_I32 "\n", (int)it.first.size(), it.first.data(), *std::get<int32_t*>(it.second)); + } else if(std::holds_alternative<ConfigHotkey*>(it.second)) { + const ConfigHotkey *config_hotkey = std::get<ConfigHotkey*>(it.second); + fprintf(file, "%.*s " FORMAT_I64 " " FORMAT_U32 "\n", (int)it.first.size(), it.first.data(), config_hotkey->keysym, config_hotkey->modifiers); + } else if(std::holds_alternative<ConfigHotkey*>(it.second)) { + std::vector<std::string> *array = std::get<std::vector<std::string>*>(it.second); + for(const std::string &value : *array) { + fprintf(file, "%.*s %s\n", (int)it.first.size(), it.first.data(), value.c_str()); + } + } + } + + fclose(file); + } +}
\ No newline at end of file diff --git a/src/GsrInfo.cpp b/src/GsrInfo.cpp index f147ecb..dfd18af 100644 --- a/src/GsrInfo.cpp +++ b/src/GsrInfo.cpp @@ -1,57 +1,38 @@ #include "../include/GsrInfo.hpp" +#include "../include/Utils.hpp" +#include <optional> #include <string.h> -#include <functional> namespace gsr { - using StringSplitCallback = std::function<bool(std::string_view line)>; - - static void string_split_char(const std::string &str, char delimiter, StringSplitCallback callback_func) { - size_t index = 0; - while(index < str.size()) { - size_t new_index = str.find(delimiter, index); - if(new_index == std::string::npos) - new_index = str.size(); - - if(!callback_func({str.data() + index, new_index - index})) - break; - - index = new_index + 1; - } - } - - static void parse_system_info_line(GsrInfo *gsr_info, const std::string &line) { - const size_t space_index = line.find(' '); - if(space_index == std::string::npos) + static void parse_system_info_line(GsrInfo *gsr_info, std::string_view line) { + const std::optional<KeyValue> key_value = parse_key_value(line); + if(!key_value) return; - const std::string_view attribute_name = {line.c_str(), space_index}; - const std::string_view attribute_value = {line.c_str() + space_index + 1, line.size() - (space_index + 1)}; - if(attribute_name == "display_server") { - if(attribute_value == "x11") + if(key_value->key == "display_server") { + if(key_value->value == "x11") gsr_info->system_info.display_server = DisplayServer::X11; - else if(attribute_value == "wayland") + else if(key_value->value == "wayland") gsr_info->system_info.display_server = DisplayServer::WAYLAND; } } - static void parse_gpu_info_line(GsrInfo *gsr_info, const std::string &line) { - const size_t space_index = line.find(' '); - if(space_index == std::string::npos) + static void parse_gpu_info_line(GsrInfo *gsr_info, std::string_view line) { + const std::optional<KeyValue> key_value = parse_key_value(line); + if(!key_value) return; - const std::string_view attribute_name = {line.c_str(), space_index}; - const std::string_view attribute_value = {line.c_str() + space_index + 1, line.size() - (space_index + 1)}; - if(attribute_name == "vendor") { - if(attribute_value == "amd") + if(key_value->key == "vendor") { + if(key_value->value == "amd") gsr_info->gpu_info.vendor = GpuVendor::AMD; - else if(attribute_value == "intel") + else if(key_value->value == "intel") gsr_info->gpu_info.vendor = GpuVendor::INTEL; - else if(attribute_value == "nvidia") + else if(key_value->value == "nvidia") gsr_info->gpu_info.vendor = GpuVendor::NVIDIA; } } - static void parse_video_codecs_line(GsrInfo *gsr_info, const std::string &line) { + static void parse_video_codecs_line(GsrInfo *gsr_info, std::string_view line) { if(line == "h264") gsr_info->supported_video_codecs.h264 = true; else if(line == "h264_software") @@ -66,19 +47,23 @@ namespace gsr { gsr_info->supported_video_codecs.vp9 = true; } - static GsrMonitor capture_option_line_to_monitor(const std::string &line) { - size_t space_index = line.find(' '); - if(space_index == std::string::npos) - return { line, {0, 0} }; + static std::optional<GsrMonitor> capture_option_line_to_monitor(std::string_view line) { + std::optional<GsrMonitor> monitor; + const std::optional<KeyValue> key_value = parse_key_value(line); + if(!key_value) + return monitor; + + char value_buffer[256]; + snprintf(value_buffer, sizeof(value_buffer), "%.*s", (int)key_value->value.size(), key_value->value.data()); - mgl::vec2i size = {0, 0}; - if(sscanf(line.c_str() + space_index + 1, "%dx%d", &size.x, &size.y) != 2) - size = {0, 0}; + monitor = GsrMonitor{std::string(key_value->key), mgl::vec2i{0, 0}}; + if(sscanf(value_buffer, "%dx%d", &monitor->size.x, &monitor->size.y) != 2) + monitor->size = {0, 0}; - return { line.substr(0, space_index), size }; + return monitor; } - static void parse_capture_options_line(GsrInfo *gsr_info, const std::string &line) { + static void parse_capture_options_line(GsrInfo *gsr_info, std::string_view line) { if(line == "window") gsr_info->supported_capture_options.window = true; else if(line == "focused") @@ -87,8 +72,11 @@ namespace gsr { gsr_info->supported_capture_options.screen = true; else if(line == "portal") gsr_info->supported_capture_options.portal = true; - else - gsr_info->supported_capture_options.monitors.push_back(capture_option_line_to_monitor(line)); + else { + std::optional<GsrMonitor> monitor = capture_option_line_to_monitor(line); + if(monitor) + gsr_info->supported_capture_options.monitors.push_back(std::move(monitor.value())); + } } enum class GsrInfoSection { @@ -99,7 +87,7 @@ namespace gsr { CAPTURE_OPTIONS }; - static bool starts_with(const std::string &str, const char *substr) { + static bool starts_with(std::string_view str, const char *substr) { size_t len = strlen(substr); return str.size() >= len && memcmp(str.data(), substr, len) == 0; } @@ -123,18 +111,16 @@ namespace gsr { output[bytes_read] = '\0'; GsrInfoSection section = GsrInfoSection::UNKNOWN; - string_split_char(output, '\n', [&](std::string_view line) { - const std::string line_str(line.data(), line.size()); - - if(starts_with(line_str, "section=")) { - const char *section_name = line_str.c_str() + 8; - if(strcmp(section_name, "system_info") == 0) + string_split_char({output, (size_t)bytes_read}, '\n', [&](std::string_view line) { + if(starts_with(line, "section=")) { + const std::string_view section_name = line.substr(8); + if(section_name == "system_info") section = GsrInfoSection::SYSTEM_INFO; - else if(strcmp(section_name, "gpu_info") == 0) + else if(section_name == "gpu_info") section = GsrInfoSection::GPU_INFO; - else if(strcmp(section_name, "video_codecs") == 0) + else if(section_name == "video_codecs") section = GsrInfoSection::VIDEO_CODECS; - else if(strcmp(section_name, "capture_options") == 0) + else if(section_name == "capture_options") section = GsrInfoSection::CAPTURE_OPTIONS; else section = GsrInfoSection::UNKNOWN; @@ -146,19 +132,19 @@ namespace gsr { break; } case GsrInfoSection::SYSTEM_INFO: { - parse_system_info_line(gsr_info, line_str); + parse_system_info_line(gsr_info, line); break; } case GsrInfoSection::GPU_INFO: { - parse_gpu_info_line(gsr_info, line_str); + parse_gpu_info_line(gsr_info, line); break; } case GsrInfoSection::VIDEO_CODECS: { - parse_video_codecs_line(gsr_info, line_str); + parse_video_codecs_line(gsr_info, line); break; } case GsrInfoSection::CAPTURE_OPTIONS: { - parse_capture_options_line(gsr_info, line_str); + parse_capture_options_line(gsr_info, line); break; } } @@ -179,16 +165,13 @@ namespace gsr { return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; } - static AudioDevice parse_audio_device_line(const std::string &line) { - AudioDevice audio_device; - const size_t space_index = line.find(' '); - if(space_index == std::string::npos) + static std::optional<AudioDevice> parse_audio_device_line(std::string_view line) { + std::optional<AudioDevice> audio_device; + const std::optional<KeyValue> key_value = parse_key_value(line); + if(!key_value) return audio_device; - const std::string_view audio_input_name = {line.c_str(), space_index}; - const std::string_view audio_input_description = {line.c_str() + space_index + 1, line.size() - (space_index + 1)}; - audio_device.name.assign(audio_input_name.data(), audio_input_name.size()); - audio_device.description.assign(audio_input_description.data(), audio_input_description.size()); + audio_device = AudioDevice{std::string(key_value->key), std::string(key_value->value)}; return audio_device; } @@ -210,9 +193,10 @@ namespace gsr { } output[bytes_read] = '\0'; - string_split_char(output, '\n', [&](std::string_view line) { - const std::string line_str(line.data(), line.size()); - audio_devices.push_back(parse_audio_device_line(line_str)); + string_split_char({output, (size_t)bytes_read}, '\n', [&](std::string_view line) { + std::optional<AudioDevice> audio_device = parse_audio_device_line(line); + if(audio_device) + audio_devices.push_back(std::move(audio_device.value())); return true; }); diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 0000000..4252de8 --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,176 @@ +#include "../include/Utils.hpp" +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <pwd.h> +#include <limits.h> +#include <string.h> +#include <sys/stat.h> + +namespace gsr { + void string_split_char(std::string_view str, char delimiter, StringSplitCallback callback_func) { + size_t index = 0; + while(index < str.size()) { + size_t new_index = str.find(delimiter, index); + if(new_index == std::string_view::npos) + new_index = str.size(); + + if(!callback_func(str.substr(index, new_index - index))) + break; + + index = new_index + 1; + } + } + + std::optional<KeyValue> parse_key_value(std::string_view line) { + const size_t space_index = line.find(' '); + if(space_index == std::string_view::npos) + return std::nullopt; + return KeyValue{line.substr(0, space_index), line.substr(space_index + 1)}; + } + + std::string get_home_dir() { + const char *home_dir = getenv("HOME"); + if(!home_dir) { + passwd *pw = getpwuid(getuid()); + home_dir = pw->pw_dir; + } + + if(!home_dir) { + fprintf(stderr, "Error: Failed to get home directory of user, using /tmp directory\n"); + home_dir = "/tmp"; + } + + return home_dir; + } + + std::string get_config_dir() { + std::string config_dir; + const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + if(xdg_config_home) { + config_dir = xdg_config_home; + } else { + config_dir = get_home_dir() + "/.config"; + } + config_dir += "/gpu-screen-recorder"; + return config_dir; + } + + // Whoever designed xdg-user-dirs is retarded. Why are some XDG variables environment variables + // while others are in this pseudo shell config file ~/.config/user-dirs.dirs + std::map<std::string, std::string> get_xdg_variables() { + std::string user_dirs_filepath; + const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + if(xdg_config_home) { + user_dirs_filepath = xdg_config_home; + } else { + user_dirs_filepath = get_home_dir() + "/.config"; + } + + user_dirs_filepath += "/user-dirs.dirs"; + + std::map<std::string, std::string> result; + FILE *f = fopen(user_dirs_filepath.c_str(), "rb"); + if(!f) + return result; + + char line[PATH_MAX]; + while(fgets(line, sizeof(line), f)) { + int len = strlen(line); + if(len < 2) + continue; + + if(line[0] == '#') + continue; + + if(line[len - 1] == '\n') { + line[len - 1] = '\0'; + len--; + } + + if(line[len - 1] != '"') + continue; + + line[len - 1] = '\0'; + len--; + + const char *sep = strchr(line, '='); + if(!sep) + continue; + + if(sep[1] != '\"') + continue; + + std::string value(sep + 2); + if(strncmp(value.c_str(), "$HOME/", 6) == 0) + value = get_home_dir() + value.substr(5); + + std::string key(line, sep - line); + result[std::move(key)] = std::move(value); + } + + fclose(f); + return result; + } + + std::string get_videos_dir() { + auto xdg_vars = get_xdg_variables(); + std::string xdg_videos_dir = xdg_vars["XDG_VIDEOS_DIR"]; + if(xdg_videos_dir.empty()) + xdg_videos_dir = get_home_dir() + "/Videos"; + return xdg_videos_dir; + } + + int create_directory_recursive(char *path) { + int path_len = strlen(path); + char *p = path; + char *end = path + path_len; + for(;;) { + char *slash_p = strchr(p, '/'); + + // Skips first '/', we don't want to try and create the root directory + if(slash_p == path) { + ++p; + continue; + } + + if(!slash_p) + slash_p = end; + + char prev_char = *slash_p; + *slash_p = '\0'; + int err = mkdir(path, S_IRWXU); + *slash_p = prev_char; + + if(err == -1 && errno != EEXIST) + return err; + + if(slash_p == end) + break; + else + p = slash_p + 1; + } + return 0; + } + + bool file_get_content(const char *filepath, std::string &file_content) { + file_content.clear(); + bool success = false; + + FILE *file = fopen(filepath, "rb"); + if(!file) + return success; + + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + if(file_size != -1) { + file_content.resize(file_size); + fseek(file, 0, SEEK_SET); + if((long)fread(&file_content[0], 1, file_size, file) == file_size) + success = true; + } + + fclose(file); + return success; + } +}
\ No newline at end of file diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index e8137c9..9662c34 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -18,10 +18,8 @@ namespace gsr { bool Button::on_event(mgl::Event &event, mgl::Window&, mgl::vec2f offset) { const mgl::vec2f item_size = get_size().floor(); - if(event.type == mgl::Event::MouseMoved) { - mouse_inside = mgl::FloatRect(position + offset, item_size).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y }); - } else if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { - const bool clicked_inside = mgl::FloatRect(position + offset, item_size).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y });; + if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { + const bool clicked_inside = mgl::FloatRect(position + offset, item_size).contains({ (float)event.mouse_button.x, (float)event.mouse_button.y }); if(clicked_inside && on_click) on_click(); } diff --git a/src/main.cpp b/src/main.cpp index a6ca0d8..4ec211b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,7 +253,7 @@ static void add_widgets_to_settings_page(mgl::vec2i window_size, mgl::vec2f sett auto audio_device_list = std::make_unique<gsr::List>(gsr::List::Orientation::HORIZONTAL, gsr::List::Alignment::CENTER); gsr::List *audio_device_list_ptr = audio_device_list.get(); { - audio_device_list->add_widget(std::make_unique<gsr::Label>(&gsr::get_theme().body_font, "*", gsr::get_theme().text_color)); + audio_device_list->add_widget(std::make_unique<gsr::Label>(&gsr::get_theme().body_font, " ", gsr::get_theme().text_color)); auto audio_device_box = std::make_unique<gsr::ComboBox>(&gsr::get_theme().body_font); for(const auto &audio_device : audio_devices) { audio_device_box->add_item(audio_device.description, audio_device.name); |