#include "../include/Config.hpp" #include "../include/Storage.hpp" #include #include #include // TODO: Show nice error message if config looks wrong (wrong types, has configs that do not exist (maybe spelling mistake?)) namespace QuickMedia { static bool config_initialized = false; static Config *config = nullptr; static float scale = 1.0f; static bool scale_set = false; static const int XFT_DPI_DEFAULT = 96; // Returns XFT_DPI_DEFAULT on error static int xrdb_get_dpi() { int xft_dpi = XFT_DPI_DEFAULT; Display *display = XOpenDisplay(nullptr); if(!display) { fprintf(stderr, "Failed to open x display\n"); return xft_dpi; } char *dpi = XGetDefault(display, "Xft", "dpi"); if(dpi) { xft_dpi = strtol(dpi, nullptr, 10); if(xft_dpi == 0) xft_dpi = XFT_DPI_DEFAULT; } XCloseDisplay(display); return xft_dpi; } static float get_ui_scale() { if(scale_set) return scale; const char *gdk_scale = getenv("GDK_SCALE"); if(gdk_scale) { setlocale(LC_ALL, "C"); // Sigh... stupid C scale = atof(gdk_scale); } else { scale = (float)xrdb_get_dpi() / (float)XFT_DPI_DEFAULT; } if(scale < 0.0001f) scale = 1.0f; scale_set = true; return scale; } static std::string path_expanduser(const std::string &path) { std::string result; if(!path.empty() && path[0] == '~') result = get_home_dir().data + path.substr(1); else result = path; return result; } template static void get_json_value(const Json::Value &json_obj, const char *field_name, T &val) { const Json::Value &json_val = json_obj[field_name]; if(json_val.isNull()) return; if(!json_val.is()) { fprintf(stderr, "Warning: config variable \"%s\" is not a %s\n", field_name, typeid(T).name()); return; } val = json_val.as(); } static void get_json_value(const Json::Value &json_obj, const char *field_name, float &val) { const Json::Value &json_val = json_obj[field_name]; if(json_val.isNull()) return; if(!json_val.isDouble()) { fprintf(stderr, "Warning: config variable \"%s\" is not a float\n", field_name); return; } val = json_val.asDouble(); } static void get_json_value_path(const Json::Value &json_obj, const char *field_name, std::string &val) { get_json_value(json_obj, field_name, val); path_expanduser(val); while(!val.empty() && val.back() == '/') { val.pop_back(); } } static void matrix_known_homeservers_fallback() { config->matrix.known_homeservers.insert(config->matrix.known_homeservers.end(), { "midov.pl", "matrix.org", "kde.org", "librem.one", "maunium.net", "halogen.city", "gnome.org", "shivering-isles.com", "nerdsin.space", "glowers.club", "privacytools.io", "linuxdelta.com", "tchncs.de", "jupiterbroadcasting.com" }); } static void peertube_known_instances_fallback() { config->peertube.known_instances.insert(config->peertube.known_instances.end(), { "https://tube.midov.pl", "https://videos.lukesmith.xyz", "https://peertube.se", "https://bittube.video", "https://video.nobodyhasthe.biz", "https://libre.tube", "https://open.tube", "https://runtube.re", "https://tube.kenfm.de", "https://tcode.kenfm.de", "https://tube.querdenken-711.de", "https://peertube.rage.lol", "https://gegenstimme.tv", "https://tv.poa.st", "https://libre.video", "https://gorf.tube", "https://neogenesis.tv" }); } static void config_load_fail_fallback() { matrix_known_homeservers_fallback(); peertube_known_instances_fallback(); } // No-op if this has already been called before static void init_config() { if(config_initialized) return; setlocale(LC_ALL, "C"); // Sigh... stupid C config_initialized = true; // Wtf? can't use static non-pointer config because it causes a segfault when setting config.theme. // It looks like a libc bug??? crashes for both gcc and clang. config = new Config(); config->scale = get_ui_scale(); Path config_path = get_storage_dir().join("config.json"); if(get_file_type(config_path) != FileType::REGULAR) { config_load_fail_fallback(); return; } Json::Value json_root; if(!read_file_as_json(config_path, json_root) || !json_root.isObject()) { fprintf(stderr, "Warning: failed to parse config file: %s\n", config_path.data.c_str()); config_load_fail_fallback(); return; } const Json::Value &search_json = json_root["search"]; if(search_json.isObject()) get_json_value(search_json, "font_size", config->search.font_size); const Json::Value &tab_json = json_root["tab"]; if(tab_json.isObject()) get_json_value(tab_json, "font_size", config->tab.font_size); const Json::Value &body_json = json_root["body"]; if(body_json.isObject()) { get_json_value(body_json, "title_font_size", config->body.title_font_size); get_json_value(body_json, "author_font_size", config->body.author_font_size); get_json_value(body_json, "description_font_size", config->body.description_font_size); get_json_value(body_json, "timestamp_font_size", config->body.timestamp_font_size); get_json_value(body_json, "reaction_font_size", config->body.reaction_font_size); get_json_value(body_json, "embedded_load_font_size", config->body.embedded_load_font_size); } const Json::Value &input_json = json_root["input"]; if(input_json.isObject()) get_json_value(input_json, "font_size", config->input.font_size); const Json::Value &video_json = json_root["video"]; if(video_json.isObject()) get_json_value(video_json, "max_height", config->video.max_height); const Json::Value &local_manga_json = json_root["local_manga"]; if(local_manga_json.isObject()) { get_json_value_path(local_manga_json, "directory", config->local_manga.directory); get_json_value(local_manga_json, "load_progress", config->local_manga.sort_by_name); get_json_value(local_manga_json, "sort_chapters_by_name", config->local_manga.sort_chapters_by_name); } const Json::Value &local_anime_json = json_root["local_anime"]; if(local_anime_json.isObject()) { get_json_value_path(local_anime_json, "directory", config->local_anime.directory); get_json_value(local_anime_json, "sort_by_name", config->local_anime.sort_by_name); get_json_value(local_anime_json, "auto_group_episodes", config->local_anime.auto_group_episodes); } const Json::Value &youtube_json = json_root["youtube"]; if(youtube_json.isObject()) get_json_value(youtube_json, "load_progress", config->youtube.load_progress); bool has_known_matrix_homeservers_config = false; const Json::Value &matrix_json = json_root["matrix"]; if(matrix_json.isObject()) { const Json::Value &known_homeservers_json = matrix_json["known_homeservers"]; if(known_homeservers_json.isArray()) { has_known_matrix_homeservers_config = true; for(const Json::Value &known_homeserver : known_homeservers_json) { if(!known_homeserver.isString()) { fprintf(stderr, "Warning: matrix.known_homeservers config contains non string value\n"); continue; } config->matrix.known_homeservers.push_back(known_homeserver.asString()); } } get_json_value(matrix_json, "gpg_user_id", config->matrix.gpg_user_id); } if(!has_known_matrix_homeservers_config) matrix_known_homeservers_fallback(); bool has_known_peertube_homeservers_config = false; const Json::Value &peertube_json = json_root["peertube"]; if(peertube_json.isObject()) { const Json::Value &known_instances_json = peertube_json["known_instances"]; if(known_instances_json.isArray()) { has_known_peertube_homeservers_config = true; for(const Json::Value &known_instance : known_instances_json) { if(!known_instance.isString()) { fprintf(stderr, "Warning: peertube.known_instances config contains non string value\n"); continue; } config->peertube.known_instances.push_back(known_instance.asString()); } } } if(!has_known_peertube_homeservers_config) peertube_known_instances_fallback(); const std::string home_dir = get_home_dir().data; struct DownloadPaths { const char *json_field; std::string fallback_dir; const char *xdg_var_name; std::string *config_var; }; const int num_download_paths = 4; DownloadPaths download_paths_list[num_download_paths] = { { "video_directory", home_dir + "/Videos", "XDG_VIDEOS_DIR", &config->download.video_directory }, { "image_directory", home_dir + "/Pictures", "XDG_PICTURES_DIR", &config->download.image_directory }, { "music_directory", home_dir + "/Music", "XDG_MUSIC_DIR", &config->download.music_directory }, { "file_directory", home_dir + "/Downloads", "XDG_DOWNLOAD_DIR", &config->download.file_directory }, }; const Json::Value &download_json = json_root["download"]; if(download_json.isObject()) { for(const DownloadPaths &download_paths : download_paths_list) { const Json::Value &directory_json = download_json[download_paths.json_field]; if(!directory_json.isString()) { fprintf(stderr, "Warning: config variable config.download.%s is not a string, using path \"%s\" or \"%s\" instead\n", download_paths.json_field, download_paths.xdg_var_name, download_paths.fallback_dir.c_str()); continue; } *download_paths.config_var = path_expanduser(directory_json.asString()); } } for(const DownloadPaths &download_paths : download_paths_list) { if(download_paths.config_var->empty()) { std::string dir = download_paths.fallback_dir; const char *xdg_var = getenv(download_paths.xdg_var_name); if(xdg_var) dir = xdg_var; *download_paths.config_var = std::move(dir); } } const Json::Value &font_json = json_root["font"]; if(font_json.isObject()) { get_json_value_path(font_json, "latin", config->font.latin); get_json_value_path(font_json, "latin_bold", config->font.latin_bold); get_json_value_path(font_json, "latin_monospace", config->font.latin_monospace); get_json_value_path(font_json, "cjk", config->font.cjk); get_json_value_path(font_json, "symbols", config->font.symbols); } const Json::Value &mangadex_json = json_root["mangadex"]; if(mangadex_json.isObject()) { get_json_value(mangadex_json, "allow_hentai", config->mangadex.allow_hentai); } get_json_value(json_root, "use_system_fonts", config->use_system_fonts); get_json_value(json_root, "use_system_mpv_config", config->use_system_mpv_config); get_json_value(json_root, "enable_shaders", config->enable_shaders); get_json_value(json_root, "theme", config->theme); get_json_value(json_root, "scale", config->scale); get_json_value(json_root, "font_scale", config->font_scale); get_json_value(json_root, "spacing_scale", config->spacing_scale); } const Config& get_config() { init_config(); return *config; } }