From 0d39cfc69224ce681f18a6235c56bef0c0df815d Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Sun, 29 Dec 2013 00:03:57 +0200 Subject: [PATCH] Wxwidgets: Plugin manager --- include/core/loadlib.hpp | 2 +- include/library/directory.hpp | 1 + include/platform/wxwidgets/platform.hpp | 1 + manual.lyx | 9 + manual.txt | 8 +- src/core/loadlib.cpp | 22 +- src/library/directory.cpp | 6 + src/library/zip.cpp | 2 +- src/platform/wxwidgets/editor-plugin.cpp | 326 +++++++++++++++++++++++ src/platform/wxwidgets/main.cpp | 12 +- src/platform/wxwidgets/mainwindow.cpp | 5 + 11 files changed, 387 insertions(+), 7 deletions(-) create mode 100644 src/platform/wxwidgets/editor-plugin.cpp diff --git a/include/core/loadlib.hpp b/include/core/loadlib.hpp index 6091aed1..b46bee3d 100644 --- a/include/core/loadlib.hpp +++ b/include/core/loadlib.hpp @@ -4,7 +4,7 @@ #include "library/loadlib.hpp" void handle_post_loadlibrary(); -void autoload_libraries(); +void autoload_libraries(void(*on_error)(const std::string& err) = NULL); void with_loaded_library(const loadlib::module& l); #endif diff --git a/include/library/directory.hpp b/include/library/directory.hpp index 84072915..76bd91ae 100644 --- a/include/library/directory.hpp +++ b/include/library/directory.hpp @@ -8,6 +8,7 @@ std::set enumerate_directory(const std::string& dir, const std::str std::string get_absolute_path(const std::string& relative); uintmax_t file_get_size(const std::string& path); time_t file_get_mtime(const std::string& path); +bool file_exists(const std::string& filename); bool file_is_regular(const std::string& filename); bool file_is_directory(const std::string& filename); bool ensure_directory_exists(const std::string& path); diff --git a/include/platform/wxwidgets/platform.hpp b/include/platform/wxwidgets/platform.hpp index f1a34452..2db190a0 100644 --- a/include/platform/wxwidgets/platform.hpp +++ b/include/platform/wxwidgets/platform.hpp @@ -63,6 +63,7 @@ void wxeditor_tasinput_display(wxWindow* parent); void wxeditor_macro_display(wxWindow* parent); void wxeditor_hexedit_display(wxWindow* parent); void wxeditor_multitrack_display(wxWindow* parent); +bool wxeditor_plugin_manager_display(wxWindow* parent); //Auxillary windows. void wxwindow_memorysearch_display(); diff --git a/manual.lyx b/manual.lyx index 95178362..db9d257d 100644 --- a/manual.lyx +++ b/manual.lyx @@ -227,6 +227,15 @@ Load as movie or savestate file on startup. Instead of starting the emulator, only display the settings. \end_layout +\begin_layout Subsubsection +--pluginmanager +\end_layout + +\begin_layout Standard +Instead of starting the emulator, display the plugin manager (useful to + disable some plugin that causes emulator to crash on startup) +\end_layout + \begin_layout Subsubsection --lua= \end_layout diff --git a/manual.txt b/manual.txt index f7ced34d..d566c93a 100644 --- a/manual.txt +++ b/manual.txt @@ -83,7 +83,13 @@ Load as movie or savestate file on startup. Instead of starting the emulator, only display the settings. -4.1.5 --lua= +4.1.5 --pluginmanager + +Instead of starting the emulator, display the plugin manager +(useful to disable some plugin that causes emulator to crash on +startup) + +4.1.6 --lua= Run this Lua file on startup diff --git a/src/core/loadlib.cpp b/src/core/loadlib.cpp index cff66224..1f73b0c8 100644 --- a/src/core/loadlib.cpp +++ b/src/core/loadlib.cpp @@ -29,12 +29,28 @@ void with_loaded_library(const loadlib::module& l) } } -void autoload_libraries() +void autoload_libraries(void(*on_error)(const std::string& err)) { try { + std::string extension = loadlib::library::extension(); auto libs = enumerate_directory(get_config_path() + "/autoload", ".*"); - for(auto i : libs) - with_loaded_library(*new loadlib::module(loadlib::library(i))); + for(auto i : libs) { + if(i.length() < extension.length() + 1) + continue; + if(i[i.length() - extension.length() - 1] != '.') + continue; + std::string tmp = i; + if(tmp.substr(i.length() - extension.length()) != extension) + continue; + try { + with_loaded_library(*new loadlib::module(loadlib::library(i))); + } catch(std::exception& e) { + std::string x = "Can't load '" + i + "': " + e.what(); + if(on_error) + on_error(x); + messages << x << std::endl; + } + } handle_post_loadlibrary(); } catch(std::exception& e) { messages << e.what() << std::endl; diff --git a/src/library/directory.cpp b/src/library/directory.cpp index 9cc7907b..d423d730 100644 --- a/src/library/directory.cpp +++ b/src/library/directory.cpp @@ -41,6 +41,12 @@ time_t file_get_mtime(const std::string& path) return boost_fs::last_write_time(boost_fs::path(path)); } +bool file_exists(const std::string& filename) +{ + boost::system::error_code ec; + return boost_fs::exists(boost_fs::path(filename), ec); +} + bool file_is_regular(const std::string& filename) { boost::system::error_code ec; diff --git a/src/library/zip.cpp b/src/library/zip.cpp index 7168fa53..0e49502f 100644 --- a/src/library/zip.cpp +++ b/src/library/zip.cpp @@ -23,7 +23,7 @@ namespace zip int rename_overwrite(const char* oldname, const char* newname) { #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE) - return MoveFileEx(oldname, newname, MOVEFILE_REPLACE_EXISTING); + return MoveFileEx(oldname, newname, MOVEFILE_REPLACE_EXISTING) ? 0 : -1; #else return rename(oldname, newname); #endif diff --git a/src/platform/wxwidgets/editor-plugin.cpp b/src/platform/wxwidgets/editor-plugin.cpp new file mode 100644 index 00000000..46be8b63 --- /dev/null +++ b/src/platform/wxwidgets/editor-plugin.cpp @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include +#include "platform/wxwidgets/platform.hpp" +#include "platform/wxwidgets/loadsave.hpp" +#include "core/misc.hpp" +#include "core/window.hpp" +#include "library/directory.hpp" +#include "library/loadlib.hpp" +#include "library/string.hpp" +#include "library/zip.hpp" +#include +#include +#include +#if defined(_WIN32) || defined(_WIN64) +#else +#include +#endif + +namespace +{ + std::string get_name(std::string path) + { +#if defined(_WIN32) || defined(_WIN64) + const char* sep = "\\/"; +#else + const char* sep = "/"; +#endif + size_t p = path.find_last_of(sep); + std::string name; + if(p == std::string::npos) + name = path; + else + name = path.substr(p + 1); + return name; + } + + std::string strip_extension(std::string tmp, std::string ext) + { + regex_results r = regex("(.*)\\." + ext + "(|\\.disabled)", tmp); + if(!r) return tmp; + return r[1]; + } +} + +class wxeditor_plugins : public wxDialog +{ +public: + wxeditor_plugins(wxWindow* parent); + void on_selection_change(wxCommandEvent& e); + void on_add(wxCommandEvent& e); + void on_rename(wxCommandEvent& e); + void on_enable(wxCommandEvent& e); + void on_delete(wxCommandEvent& e); + void on_start(wxCommandEvent& e); + void on_close(wxCommandEvent& e); +private: + void reload_plugins(); + wxListBox* plugins; + wxButton* addbutton; + wxButton* renamebutton; + wxButton* enablebutton; + wxButton* deletebutton; + wxButton* startbutton; + wxButton* closebutton; + std::vector> pluginstbl; + std::string extension; + std::string pathpfx; +}; + +wxeditor_plugins::wxeditor_plugins(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxT("lsnes: Plugin manager"), wxDefaultPosition, wxSize(-1, -1)) +{ + Center(); + wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); + SetSizer(top_s); + pathpfx = get_config_path() + "/autoload"; + extension = loadlib::library::extension(); + + top_s->Add(plugins = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(400, 300)), 1, wxGROW); + plugins->Connect(wxEVT_COMMAND_LISTBOX_SELECTED, + wxCommandEventHandler(wxeditor_plugins::on_selection_change), NULL, this); + + wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + pbutton_s->Add(addbutton = new wxButton(this, wxID_ANY, wxT("Add")), 0, wxGROW); + pbutton_s->Add(renamebutton = new wxButton(this, wxID_ANY, wxT("Rename")), 0, wxGROW); + pbutton_s->Add(enablebutton = new wxButton(this, wxID_ANY, wxT("Enable")), 0, wxGROW); + pbutton_s->Add(deletebutton = new wxButton(this, wxID_ANY, wxT("Delete")), 0, wxGROW); + pbutton_s->AddStretchSpacer(); + if(!parent) + pbutton_s->Add(startbutton = new wxButton(this, wxID_ANY, wxT("Start")), 0, wxGROW); + else + startbutton = NULL; + if(!parent) + pbutton_s->Add(closebutton = new wxButton(this, wxID_EXIT, wxT("Quit")), 0, wxGROW); + else + pbutton_s->Add(closebutton = new wxButton(this, wxID_ANY, wxT("Close")), 0, wxGROW); + addbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_add), NULL, + this); + renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_rename), NULL, + this); + enablebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_enable), NULL, + this); + deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_delete), NULL, + this); + if(startbutton) + startbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_start), + NULL, this); + closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_close), NULL, + this); + top_s->Add(pbutton_s, 0, wxGROW); + reload_plugins(); + wxCommandEvent e; + on_selection_change(e); + Fit(); +} + +void wxeditor_plugins::reload_plugins() +{ + int sel = plugins->GetSelection(); + std::string name; + if(sel == wxNOT_FOUND || sel >= pluginstbl.size()) + name = ""; + else + name = pluginstbl[sel].first; + + auto dir = enumerate_directory(pathpfx, ".*\\." + extension + "(|\\.disabled)"); + plugins->Clear(); + pluginstbl.clear(); + for(auto i : dir) { + regex_results r = regex("(.*)\\." + extension + "(|\\.disabled)", get_name(i)); + if(!r) continue; + pluginstbl.push_back(std::make_pair(r[1], r[2] == "")); + std::string r1 = r[1]; + plugins->Append(towxstring(r1 + ((r[2] == "") ? "" : " (disabled)"))); + } + + bool found = false; + for(size_t i = 0; i < pluginstbl.size(); i++) { + if(pluginstbl[i].first == name) + plugins->SetSelection(i); + } + wxCommandEvent e; + on_selection_change(e); +} + +void wxeditor_plugins::on_selection_change(wxCommandEvent& e) +{ + int sel = plugins->GetSelection(); + if(sel == wxNOT_FOUND || sel >= pluginstbl.size()) { + renamebutton->Enable(false); + enablebutton->Enable(false); + deletebutton->Enable(false); + } else { + enablebutton->SetLabel(towxstring(pluginstbl[sel].second ? "Disable" : "Enable")); + renamebutton->Enable(true); + enablebutton->Enable(true); + deletebutton->Enable(true); + } +} + +void wxeditor_plugins::on_add(wxCommandEvent& e) +{ + try { + std::string file = choose_file_load(this, "Choose plugin to add", ".", + single_type(loadlib::library::extension(), loadlib::library::name())); + std::string name = strip_extension(get_name(file), extension); + std::string nname = pathpfx + "/" + name + "." + extension; + bool overwrite_ok = false; + bool first = true; + int counter = 2; + while(!overwrite_ok && (file_exists(nname) || file_exists(nname + ".disabled"))) { + if(first) { + wxMessageDialog* d3 = new wxMessageDialog(this, + towxstring("Plugin '" + name + "' already exists.\n\nOverwrite?"), + towxstring("Plugin already exists"), + wxYES_NO | wxCANCEL | wxNO_DEFAULT | wxICON_QUESTION); + int r = d3->ShowModal(); + d3->Destroy(); + first = false; + if(r == wxID_YES) + break; + if(r == wxID_CANCEL) { + reload_plugins(); + return; + } + } + nname = pathpfx + "/" + name + "(" + (stringfmt() << counter++).str() + ")." + extension; + } + std::ifstream in(file, std::ios::binary); + std::ofstream out(nname, std::ios::binary); + if(!out) { + show_message_ok(this, "Error", "Can't write file '" + nname + "'", wxICON_EXCLAMATION); + reload_plugins(); + return; + } + if(!in) { + remove(nname.c_str()); + show_message_ok(this, "Error", "Can't read file '" + file + "'", wxICON_EXCLAMATION); + reload_plugins(); + return; + } + while(true) { + char buf[4096]; + size_t r; + r = in.readsome(buf, sizeof(buf)); + out.write(buf, r); + if(!r) + break; + } + if(!out) { + remove(nname.c_str()); + show_message_ok(this, "Error", "Can't write file '" + nname + "'", wxICON_EXCLAMATION); + reload_plugins(); + return; + } + //Set permissions. +#if defined(_WIN32) || defined(_WIN64) +#else + struct stat s; + if(stat(nname.c_str(), &s) < 0) + s.st_mode = 0644; + if(s.st_mode & 0400) s.st_mode |= 0100; + if(s.st_mode & 040) s.st_mode |= 010; + if(s.st_mode & 04) s.st_mode |= 01; + chmod(nname.c_str(), s.st_mode & 0777); +#endif + std::string disname = nname + ".disabled"; + remove(disname.c_str()); + reload_plugins(); + } catch(canceled_exception& e) { + } +} + +void wxeditor_plugins::on_rename(wxCommandEvent& e) +{ + int sel = plugins->GetSelection(); + if(sel == wxNOT_FOUND || sel >= pluginstbl.size()) + return; + std::string name = pluginstbl[sel].first; + std::string name2; + try { + name2 = pick_text(this, "Rename plugin to", "Enter new name for plugin", name, false); + } catch(canceled_exception& e) { + return; + } + std::string oname = pathpfx + "/" + name + "." + extension + (pluginstbl[sel].second ? "" : ".disabled"); + std::string nname = pathpfx + "/" + name2 + "." + extension + (pluginstbl[sel].second ? "" : ".disabled"); + if(oname != nname) + zip::rename_overwrite(oname.c_str(), nname.c_str()); + pluginstbl[sel].first = name2; + reload_plugins(); +} + +void wxeditor_plugins::on_enable(wxCommandEvent& e) +{ + int sel = plugins->GetSelection(); + if(sel == wxNOT_FOUND || sel >= pluginstbl.size()) + return; + std::string ename = pathpfx + "/" + pluginstbl[sel].first + "." + extension; + std::string dname = pathpfx + "/" + pluginstbl[sel].first + "." + extension + ".disabled"; + bool ok; + if(pluginstbl[sel].second) + ok = !zip::rename_overwrite(ename.c_str(), dname.c_str()); + else + ok = !zip::rename_overwrite(dname.c_str(), ename.c_str()); + if(!ok) { + show_message_ok(this, "Error", "Can't enable/disable plugin '" + pluginstbl[sel].first + + "'", wxICON_EXCLAMATION); + reload_plugins(); + return; + } + pluginstbl[sel].second = !pluginstbl[sel].second; + reload_plugins(); +} + +void wxeditor_plugins::on_delete(wxCommandEvent& e) +{ + int sel = plugins->GetSelection(); + if(sel == wxNOT_FOUND || sel >= pluginstbl.size()) + return; + std::string oname = pathpfx + "/" + pluginstbl[sel].first + "." + extension + + (pluginstbl[sel].second ? "" : ".disabled"); + if(remove(oname.c_str()) < 0) { + int err = errno; + show_message_ok(this, "Error", "Can't delete plugin '" + pluginstbl[sel].first + + "': " + strerror(err), wxICON_EXCLAMATION); + reload_plugins(); + return; + } + reload_plugins(); +} + +void wxeditor_plugins::on_start(wxCommandEvent& e) +{ + EndModal(wxID_OK); +} + +void wxeditor_plugins::on_close(wxCommandEvent& e) +{ + EndModal(wxID_CANCEL); +} + +bool wxeditor_plugin_manager_display(wxWindow* parent) +{ + int r; + modal_pause_holder* hld = NULL; + try { + if(parent) + hld = new modal_pause_holder(); + wxDialog* editor; + try { + editor = new wxeditor_plugins(parent); + r = editor->ShowModal(); + } catch(...) { + } + editor->Destroy(); + if(hld) delete hld; + return (r == wxID_OK); + } catch(...) { + if(hld) delete hld; + return false; + } +} diff --git a/src/platform/wxwidgets/main.cpp b/src/platform/wxwidgets/main.cpp index ba3beb91..86f4c389 100644 --- a/src/platform/wxwidgets/main.cpp +++ b/src/platform/wxwidgets/main.cpp @@ -344,6 +344,7 @@ public: virtual bool OnCmdLineParsed(wxCmdLineParser& parser); private: bool settings_mode; + bool pluginmanager_mode; std::string c_rom; std::string c_file; std::vector cmdline; @@ -374,6 +375,7 @@ bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser) regex_results r; if(i == "--help" || i == "-h") { std::cout << "--settings: Show the settings dialog" << std::endl; + std::cout << "--pluginmanager: Show the plugin manager" << std::endl; std::cout << "--rom=: Load specified ROM on startup" << std::endl; std::cout << "--load=: Load specified save/movie on starup" << std::endl; std::cout << "--lua=: Load specified Lua script on startup" << std::endl; @@ -384,6 +386,8 @@ bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser) } if(i == "--settings") settings_mode = true; + if(i == "--pluginmanager") + pluginmanager_mode = true; if(r = regex("--set=([^=]+)=(.+)", i)) c_settings[r[1]] = r[2]; if(r = regex("--lua=(.+)", i)) @@ -403,6 +407,10 @@ bool lsnes_app::OnInit() set_random_seed(); bring_app_foreground(); + if(pluginmanager_mode) + if(!wxeditor_plugin_manager_display(NULL)) + return false; + ui_services = new ui_services_type(); ui_thread = this_thread_id(); @@ -419,7 +427,9 @@ bool lsnes_app::OnInit() controls.set_ports(ports); std::string cfgpath = get_config_path(); - autoload_libraries(); + autoload_libraries([](const std::string& error) { + show_message_ok(NULL, "Error loading plugin", error, wxICON_EXCLAMATION); + }); messages << "Saving per-user data to: " << get_config_path() << std::endl; messages << "--- Loading configuration --- " << std::endl; load_configuration(); diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index 38432da7..3fdd69b5 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -133,6 +133,7 @@ enum wxID_DOWNLOAD, wxID_TRACELOG_FIRST, wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256, + wxID_PLUGIN_MANAGER, }; @@ -1025,6 +1026,7 @@ wxwin_mainwindow::wxwin_mainwindow() if(loadlib::library::name() != "") { menu_separator(); menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name())); + menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager")); } menu_separator(); menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM")); @@ -1606,6 +1608,9 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) handle_post_loadlibrary(); break; } + case wxID_PLUGIN_MANAGER: + wxeditor_plugin_manager_display(this); + return; case wxID_RELOAD_ROM_IMAGE: runemufn([]() { lsnes_cmd.invoke("unpause-emulator");