/************************************************************** switchres.cpp - Swichres manager --------------------------------------------------------- Switchres Modeline generation engine for emulation License GPL-2.0+ Copyright 2010-2021 Chris Kennedy, Antonio Giner, Alexandre Wodarczyk, Gil Delescluse **************************************************************/ #include #include #include #include "switchres.h" #include "log.h" using namespace std; const string WHITESPACE = " \n\r\t\f\v"; #if defined(_WIN32) #define SR_CONFIG_PATHS ";.\\;.\\ini\\;" #else #define SR_CONFIG_PATHS ";./;./ini/;/etc/;" #endif //============================================================ // logging //============================================================ void switchres_manager::set_log_level(int log_level) { set_log_verbosity(log_level); } void switchres_manager::set_log_verbose_fn(void *func_ptr) { set_log_verbose((void *)func_ptr); } void switchres_manager::set_log_info_fn(void *func_ptr) { set_log_info((void *)func_ptr); } void switchres_manager::set_log_error_fn(void *func_ptr) { set_log_error((void *)func_ptr); } //============================================================ // File parsing helpers //============================================================ string ltrim(const string& s) { size_t start = s.find_first_not_of(WHITESPACE); return (start == string::npos) ? "" : s.substr(start); } string rtrim(const string& s) { size_t end = s.find_last_not_of(WHITESPACE); return (end == string::npos) ? "" : s.substr(0, end + 1); } string trim(const string& s) { return rtrim(ltrim(s)); } bool get_value(const string& line, string& key, string& value) { size_t key_end = line.find_first_of(WHITESPACE); key = line.substr(0, key_end); value = ltrim(line.substr(key_end + 1)); if (key.length() > 0 && value.length() > 0) return true; return false; } constexpr unsigned int s2i(const char* str, int h = 0) { return !str[h] ? 5381 : (s2i(str, h+1)*33) ^ str[h]; } //============================================================ // switchres_manager::switchres_manager //============================================================ switchres_manager::switchres_manager() { // Create our display manager m_display_factory = new display_manager(); m_current_display = m_display_factory; // Set display manager default options display()->set_monitor("generic_15"); display()->set_modeline("auto"); display()->set_lcd_range("auto"); for (int i = 0; i++ < MAX_RANGES;) display()->set_crt_range(i, "auto"); display()->set_screen("auto"); display()->set_modeline_generation(true); display()->set_lock_unsupported_modes(true); display()->set_lock_system_modes(true); display()->set_refresh_dont_care(false); // Set modeline generator default options display()->set_interlace(true); display()->set_doublescan(true); display()->set_dotclock_min(0.0f); display()->set_monitor_aspect(STANDARD_CRT_ASPECT); display()->set_refresh_tolerance(2.0f); display()->set_super_width(2560); display()->set_h_shift(0); display()->set_v_shift(0); display()->set_h_size(1.0f); display()->set_v_shift_correct(0); display()->set_pixel_precision(1); display()->set_interlace_force_even(0); // Set logger properties set_log_info_fn((void*)printf); set_log_error_fn((void*)printf); set_log_verbose_fn((void*)printf); set_log_level(2); } //============================================================ // switchres_manager::~switchres_manager //============================================================ switchres_manager::~switchres_manager() { if (m_display_factory) delete m_display_factory; for (auto &display : displays) delete display; }; //============================================================ // switchres_manager::add_display //============================================================ display_manager* switchres_manager::add_display(bool parse_options) { // Parse display specific ini, if it exists char file_name[32] = {0}; sprintf(file_name, "display%d.ini", (int)displays.size()); bool has_ini = parse_config(file_name); // Create new display display_manager *display = m_display_factory->make(&m_display_factory->m_ds); if (display == nullptr) { log_error("Switchres: error adding display\n"); return nullptr; } m_current_display = display; display->set_index(displays.size()); display->set_has_ini(has_ini); displays.push_back(display); log_verbose("Switchres(v%s) add display[%d]\n", SWITCHRES_VERSION, display->index()); if (parse_options) display->parse_options(); return display; } //============================================================ // switchres_manager::parse_config //============================================================ bool switchres_manager::parse_config(const char *file_name) { ifstream config_file; // Search for ini file in our config paths auto start = 0U; while (true) { char full_path[256] = ""; string paths = SR_CONFIG_PATHS; auto end = paths.find(";", start); if (end == string::npos) return false; snprintf(full_path, sizeof(full_path), "%s%s", paths.substr(start, end - start).c_str(), file_name); config_file.open(full_path); if (config_file.is_open()) { log_verbose("parsing %s\n", full_path); break; } start = end + 1; } // Ini file found, parse it string line; while (getline(config_file, line)) { line = trim(line); if (line.length() == 0 || line.at(0) == '#') continue; string key, value; if(get_value(line, key, value)) set_option(key.c_str(), value.c_str()); } config_file.close(); return true; } //============================================================ // switchres_manager::set_option //============================================================ void switchres_manager::set_option(const char* key, const char* value) { switch (s2i(key)) { // Switchres options case s2i("verbose"): if (atoi(value)) set_log_verbose_fn((void*)printf); break; case s2i("monitor"): display()->set_monitor(value); break; case s2i("crt_range0"): display()->set_crt_range(0, value); break; case s2i("crt_range1"): display()->set_crt_range(1, value); break; case s2i("crt_range2"): display()->set_crt_range(2, value); break; case s2i("crt_range3"): display()->set_crt_range(3, value); break; case s2i("crt_range4"): display()->set_crt_range(4, value); break; case s2i("crt_range5"): display()->set_crt_range(5, value); break; case s2i("crt_range6"): display()->set_crt_range(6, value); break; case s2i("crt_range7"): display()->set_crt_range(7, value); break; case s2i("crt_range8"): display()->set_crt_range(8, value); break; case s2i("crt_range9"): display()->set_crt_range(9, value); break; case s2i("lcd_range"): display()->set_lcd_range(value); break; case s2i("modeline"): display()->set_modeline(value); break; case s2i("user_mode"): { modeline user_mode = {}; if (strcmp(value, "auto")) { if (sscanf(value, "%dx%d@%d", &user_mode.width, &user_mode.height, &user_mode.refresh) < 1) { log_error("Error: use format resolution x@\n"); break; } } display()->set_user_mode(&user_mode); break; } // Display options case s2i("display"): display()->set_screen(value); break; case s2i("api"): display()->set_api(value); break; case s2i("modeline_generation"): display()->set_modeline_generation(atoi(value)); break; case s2i("lock_unsupported_modes"): display()->set_lock_unsupported_modes(atoi(value)); break; case s2i("lock_system_modes"): display()->set_lock_system_modes(atoi(value)); break; case s2i("refresh_dont_care"): display()->set_refresh_dont_care(atoi(value)); break; case s2i("keep_changes"): display()->set_keep_changes(atoi(value)); break; // Modeline generation options case s2i("interlace"): display()->set_interlace(atoi(value)); break; case s2i("doublescan"): display()->set_doublescan(atoi(value)); break; case s2i("dotclock_min"): { double pclock_min = 0.0f; sscanf(value, "%lf", &pclock_min); display()->set_dotclock_min(pclock_min); break; } case s2i("sync_refresh_tolerance"): { double refresh_tolerance = 0.0f; sscanf(value, "%lf", &refresh_tolerance); display()->set_refresh_tolerance(refresh_tolerance); break; } case s2i("super_width"): { int super_width = 0; sscanf(value, "%d", &super_width); display()->set_super_width(super_width); break; } case s2i("aspect"): display()->set_monitor_aspect(value); break; case s2i("h_size"): { double h_size = 1.0f; sscanf(value, "%lf", &h_size); display()->set_h_size(h_size); break; } case s2i("h_shift"): { int h_shift = 0; sscanf(value, "%d", &h_shift); display()->set_h_shift(h_shift); break; } case s2i("v_shift"): { int v_shift = 0; sscanf(value, "%d", &v_shift); display()->set_v_shift(v_shift); break; } case s2i("v_shift_correct"): display()->set_v_shift_correct(atoi(value)); break; case s2i("pixel_precision"): display()->set_pixel_precision(atoi(value)); break; case s2i("interlace_force_even"): display()->set_interlace_force_even(atoi(value)); break; // Custom video backend options case s2i("screen_compositing"): display()->set_screen_compositing(atoi(value)); break; case s2i("screen_reordering"): display()->set_screen_reordering(atoi(value)); break; case s2i("allow_hardware_refresh"): display()->set_allow_hardware_refresh(atoi(value)); break; case s2i("custom_timing"): display()->set_custom_timing(value); break; // Various case s2i("verbosity"): { int verbosity_level = 1; sscanf(value, "%d", &verbosity_level); set_log_level(verbosity_level); break; } default: log_error("Invalid option %s\n", key); break; } } //============================================================ // switchres_manager::set_current_display //============================================================ void switchres_manager::set_current_display(int index) { int disp_index; if (index == -1) { m_current_display = m_display_factory; return; } else if (index < 0 || index >= (int)displays.size()) disp_index = 0; else disp_index = index; m_current_display = displays[disp_index]; }