diff --git a/CMakeLists.txt b/CMakeLists.txt index 70bc7f8..3c7850d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,6 +221,7 @@ if(APPLE) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} BUNDLE DESTINATION .) configure_file(share/FixBundle.cmake.in ${PROJECT_BINARY_DIR}/FixBundle.cmake @ONLY) +# TODO: how to make resources read-only (on Mac) install(SCRIPT ${PROJECT_BINARY_DIR}/FixBundle.cmake) elseif(WIN32) install(TARGETS ${APP_NAME} diff --git a/src/E2wxApp.cpp b/src/E2wxApp.cpp index 4a3b645..e208f23 100644 --- a/src/E2wxApp.cpp +++ b/src/E2wxApp.cpp @@ -178,7 +178,7 @@ bool E2wxApp::OnInit() { this->emu = new Emulator(); - Config cfg(this->arg_configfile); + Config cfg(this->arg_configfile, this->opt_config_from_prefs_only); this->emu->config(cfg); this->emu->init(); this->emu_timer = new EmuTimer(this->emu); @@ -215,8 +215,8 @@ void E2wxApp::OnFatalException() { -static const wxCmdLineEntryDesc cmdLineDesc[] = -{ +static const wxCmdLineEntryDesc cmdLineDesc[] = { + { wxCMD_LINE_SWITCH, "p", "prefs", "Read config only from preferences, never an external file.", wxCMD_LINE_VAL_NONE }, { wxCMD_LINE_PARAM, NULL, NULL, "config-file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, wxCMD_LINE_DESC_END }; @@ -231,6 +231,8 @@ bool E2wxApp::OnCmdLineParsed(wxCmdLineParser& parser) { return false; } + this->opt_config_from_prefs_only = parser.Found("p"); + const int n = parser.GetParamCount(); if (n <= 0) { diff --git a/src/E2wxApp.h b/src/E2wxApp.h index 825f653..41e8793 100644 --- a/src/E2wxApp.h +++ b/src/E2wxApp.h @@ -59,6 +59,7 @@ class E2wxApp : public wxApp { std::filesystem::path confdir; std::filesystem::path docsdir; std::filesystem::path arg_configfile; + bool opt_config_from_prefs_only; EmuTimer *emu_timer; Emulator *emu; diff --git a/src/PreferencesDialog.cpp b/src/PreferencesDialog.cpp index 7a29fb7..1a0c32e 100644 --- a/src/PreferencesDialog.cpp +++ b/src/PreferencesDialog.cpp @@ -134,13 +134,7 @@ void PreferencesDialog::BuildItemTree() { } void PreferencesDialog::OnInit() { - wxConfigBase *appconf = wxConfigBase::Get(); - if (!appconf->Read("/ActivePreferences/name", &this->active)) { - // TODO what to do when no config? - this->active = "epple2"; - appconf->Write("/ActivePreferences/name", this->active); - appconf->Flush(); - } + wxConfigBase::Get()->Read("/ActivePreferences/name", &this->active, ""); wxXmlResource::Get()->LoadDialog(this, this->parent, "Preferences"); @@ -163,6 +157,8 @@ void PreferencesDialog::Save(const std::filesystem::path& to) { } } +// TODO: enable/disable the various buttons, etc. (currently all are enabled even if not functional) + void PreferencesDialog::OnTreeSelectionChanged(wxTreeEvent& evt) { // note: we don't get the first select upon dialog creation, // nor the final de-select upon dialog destruction diff --git a/src/configep2.cpp b/src/configep2.cpp index bd66ec4..d27edbc 100644 --- a/src/configep2.cpp +++ b/src/configep2.cpp @@ -18,7 +18,7 @@ #include "configep2.h" #include "E2wxApp.h" - +#include "e2filesystem.h" #include "apple2.h" #include "memory.h" #include "memoryrandomaccess.h" @@ -36,6 +36,8 @@ #include #include +#include + #include #include #include @@ -47,6 +49,10 @@ +static const wxString DEFAULT_CONFIG_NAME{"epple2"}; + + + #define K 1024u static std::uint16_t memory_block_size(const std::string &block_size) { @@ -61,15 +67,11 @@ static std::uint16_t memory_block_size(const std::string &block_size) { -unsigned char Config::disk_mask(0); -Config::Config(const std::filesystem::path& f): - file_path(f) -{ +Config::Config(const std::filesystem::path& f, bool p): file_path {f}, prefs_only {p} { } -Config::~Config() -{ +Config::~Config() { } static void strip_comment(std::string& str) @@ -99,140 +101,211 @@ static void trim(std::string& str) } } -void Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) -{ - std::ifstream* pConfig; - std::filesystem::path path(this->file_path); - if (!path.empty()) - { - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) - { - // TODO use filename only and look in standard resources - std::stringstream ss; - ss << "Cannot open config file " << this->file_path; - throw std::runtime_error(ss.str()); +/* + * Searches for config file with the given name in the preferences areas. + * The name must not be a filesystem path. + * The actual config files themselves have ".conf" extension. + * The preferences areas are searched in this order: + * user area + * built-in area + * + * If successful, returns an open stream, caller is responsible for deleting it + * Otherwise, returns null + */ +std::ifstream *Config::openFilePref(const wxString& s_name) { + std::ifstream *ret = nullptr; + + + + BOOST_LOG_TRIVIAL(info) << "config file name was specified as: " << s_name; + std::filesystem::path path_name = path_from_string(s_name); + if (path_name.has_parent_path()) { + BOOST_LOG_TRIVIAL(error) << "invalid name for config file (paths are not allowed): " << path_name.c_str(); + return ret; + } + + if (path_name.empty()) { + BOOST_LOG_TRIVIAL(error) << "invalid: empty name for config file"; + return ret; + } + + // required file extension for any file to be recognized as a config file + path_name = path_from_string(s_name+".conf"); + + + + std::filesystem::path path; + + path = valid_input_file(path_name, wxGetApp().GetConfigDir()); + if (!path.empty()) { + ret = new std::ifstream(path); + if (ret->is_open()) { + return ret; // Found specified config file in user area of preferences } + delete ret; + ret = nullptr; + } + + path = valid_input_file(path_name, wxGetApp().GetResDir()); + if (!path.empty()) { + ret = new std::ifstream(path); + if (ret->is_open()) { + return ret; // Found specified config file in built-in area of preferences + } + delete ret; + ret = nullptr; } - if (path.empty()) - { - // TODO config file location, how to be backwardly compatible? - wxString user_config; - if (!wxConfigBase::Get()->Read("/ActivePreferences/name", &user_config)) { - // TODO what to do when no config? - user_config = "epple2"; - } - user_config += ".conf"; - std::filesystem::path user_path{user_config.wc_str()}; + return ret; +} - path = wxGetApp().GetConfigDir() / user_path; - std::cout << "looking for config file: " << path << std::endl; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) { - path = wxGetApp().GetResDir() / user_path; - std::cout << "looking for config file: " << path << std::endl; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) { - path.clear(); +std::ifstream *Config::openFileExternal(const std::filesystem::path& path) { + std::ifstream *ret = nullptr; + + const std::filesystem::path p = valid_input_file(path); + if (!p.empty()) { + ret = new std::ifstream(p); + if (ret->is_open()) { + return ret; + } + delete ret; + ret = nullptr; + } + + return ret; +} + +static const std::array rs_path_legacy{ + "./etc/epple2/epple2.conf", + ETCDIR "/epple2/epple2.conf", + "/etc/epple2/epple2.conf", + "/etc/epple2.conf", + "./epple2.conf", +}; + +std::ifstream *Config::openFileLegacy() { + std::ifstream *ret = nullptr; + + for (const auto &s_path_legacy : rs_path_legacy) { + if ((ret = openFileExternal(std::filesystem::path{s_path_legacy})) != nullptr) { + return ret; + } + } + + return ret; +} + +/* + * Algorithm to locate and open the configuration file, as specified by + * the user, either on command line, or via preferences, allowing for + * backward compatibility with legacy file locations. + */ +std::ifstream *Config::openFile() { + std::ifstream *ret = nullptr; + + if (this->file_path.empty()) { + wxString cname{}; + const bool stored_prefs_found = wxConfigBase::Get()->Read("/ActivePreferences/name", &cname, DEFAULT_CONFIG_NAME); + + if (stored_prefs_found) { + ret = openFilePref(cname); + if (ret != nullptr) { + return ret; + } + + if (!this->prefs_only) { + ret = openFileLegacy(); + if (ret != nullptr) { + return ret; + } + } + } else { + if (!this->prefs_only) { + ret = openFileLegacy(); + if (ret != nullptr) { + return ret; + } + } + ret = openFilePref(cname); + if (ret != nullptr) { + return ret; } } + } else { + if (!this->prefs_only) { + ret = openFileExternal(this->file_path); + if (ret != nullptr) { + return ret; + } + } + ret = openFilePref(this->file_path.c_str()); + if (ret != nullptr) { + return ret; + } } + return ret; +} + +void Config::parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) { + std::ifstream *p_ifstream_config = openFile(); - if (path.empty()) - { - std::cout << "standard config file location: " ETCDIR "/epple2/epple2.conf" << std::endl; - - /* - On Windows, the default directory will be - C:\Program Files\Epple2 if they start the - program from the Start Menu; therefore - etc/epple2/epple2.conf would be - C:\Program Files\epple2\etc\epple2\epple2.conf - On Linux... the current directory could be - anything, so this probably won't find it (unless - the current directory is /). - */ - path = "etc/epple2/epple2.conf"; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) - path.clear(); - } - if (path.empty()) - { - /* - This is primarily for Linux. If configured for - a PREFIX of "/usr/local", then this would be - /usr/local/etc/epple2/epple2.conf - */ - path = ETCDIR "/epple2/epple2.conf"; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) - path.clear(); - } - if (path.empty()) - { - /* - Try a likely linux location - */ - path = "/etc/epple2/epple2.conf"; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) - path.clear(); - } - if (path.empty()) - { - /* - Try another likely linux location - */ - path = "/etc/epple2.conf"; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) - path.clear(); - } - if (path.empty()) - { - /* - Last effort to find it. - */ - path = "epple2.conf"; - pConfig = new std::ifstream(path); - if (!pConfig->is_open()) - path.clear(); - } - if (path.empty()) - { + if (p_ifstream_config == nullptr) { std::cerr << "Cannot find config file. Running without any RAM, ROM, or cards." << std::endl; return; } - std::cout << "Reading configuration from file: " << path << std::endl; - std::string line; - std::getline(*pConfig,line); - while (!pConfig->eof()) - { + std::getline(*p_ifstream_config, line); + while (!p_ifstream_config->eof()) { strip_comment(line); trim(line); - if (!line.empty()) - { - parseLine(line,ram,rom,slts,revision,gui,cassetteIn,cassetteOut,apple2); + if (!line.empty()) { + // TODO "parseLine" will become Command::execute, or similar + parseLine(line, ram, rom, slts, revision, gui, cassetteIn, cassetteOut, apple2); } - std::getline(*pConfig,line); + std::getline(*p_ifstream_config, line); } - pConfig->close(); - delete pConfig; + + delete p_ifstream_config; // TODO: make sure there is no more than ONE stdin and/or ONE stdout card } + + + + + + + + + + + + + + + + + + + + + + + + + + + + void Config::parseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2) { try @@ -519,6 +592,12 @@ void Config::tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memo } } + + +unsigned char Config::disk_mask(0); + + + void Config::loadDisk(Slots& slts, int slot, int drive, const std::string& fnib) { if (drive < 1 || 2 < drive) diff --git a/src/configep2.h b/src/configep2.h index 9c3f6e7..03845e1 100644 --- a/src/configep2.h +++ b/src/configep2.h @@ -18,6 +18,7 @@ #ifndef CONFIGEP2_H #define CONFIGEP2_H +#include #include #include class Memory; @@ -34,11 +35,19 @@ public: ConfigException(const std::string& msg) : msg(msg) {} }; +// TODO split out all static things into their own class (and don't make them static) +// Remember that, besides config, also command line entry calls parseLine +// This will also help with adding menu items in place of commands class Config { private: const std::filesystem::path file_path; + const bool prefs_only; static unsigned char disk_mask; + std::ifstream *openFile(); + std::ifstream *openFilePref(const wxString& s_name); + std::ifstream *openFileExternal(const std::filesystem::path& path); + std::ifstream *openFileLegacy(); static void loadDisk(Slots& slts, int slot, int drive, const std::string& fnib); static void unloadDisk(Slots& slts, int slot, int drive); static void saveDisk(Slots& slts, int slot, int drive); @@ -46,7 +55,7 @@ private: static void tryParseLine(const std::string& line, MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2); public: - Config(const std::filesystem::path& f); + Config(const std::filesystem::path& f, bool p); ~Config(); void parse(MemoryRandomAccess& ram, Memory& rom, Slots& slts, int& revision, ScreenImage& gui, CassetteIn& cassetteIn, CassetteOut& cassetteOut, Apple2* apple2); diff --git a/src/e2filesystem.h b/src/e2filesystem.h index 82c7fc1..28ab37d 100644 --- a/src/e2filesystem.h +++ b/src/e2filesystem.h @@ -30,7 +30,7 @@ #include -std::filesystem::path valid_input_file(const std::filesystem::path path, const std::filesystem::path base); +std::filesystem::path valid_input_file(const std::filesystem::path path, const std::filesystem::path base = std::filesystem::path{}); std::filesystem::path path_from_string(const wxString& s); #endif /* E2FILESYSTEM_H */ diff --git a/src/wozfile.cpp b/src/wozfile.cpp index 5f28449..7fa8857 100644 --- a/src/wozfile.cpp +++ b/src/wozfile.cpp @@ -116,6 +116,10 @@ bool WozFile::load(const std::filesystem::path& orig_file) { printf("Reading WOZ 2.0 file: %s\n", orig_file.c_str()); std::filesystem::path filePath = valid_input_file(orig_file, wxGetApp().GetResDir()); + if (filePath.empty()) { + printf("Error opening WOZ file.\n"); + return false; + } std::ifstream *in = new std::ifstream(filePath, std::ios::binary|std::ios::in); if (!in->is_open()) { printf("Error opening file: %d\n", errno);