If initsram/initstate points to LSS file, pull the matching member
[lsnes.git] / src / platform / wxwidgets / editor-plugin.cpp
blob66ec58614386bb7e013f286b07635bb4dd5fcfbd
1 #include <wx/wx.h>
2 #include <wx/event.h>
3 #include <wx/control.h>
4 #include <wx/combobox.h>
5 #include <wx/radiobut.h>
6 #include "platform/wxwidgets/platform.hpp"
7 #include "platform/wxwidgets/loadsave.hpp"
8 #include "core/messages.hpp"
9 #include "core/misc.hpp"
10 #include "core/window.hpp"
11 #include "library/directory.hpp"
12 #include "library/filelist.hpp"
13 #include "library/loadlib.hpp"
14 #include "library/string.hpp"
15 #include "library/zip.hpp"
16 #include <iostream>
17 #include <cerrno>
18 #include <cstring>
19 #if defined(_WIN32) || defined(_WIN64)
20 #else
21 #include <sys/stat.h>
22 #endif
24 //TODO: Handle plugin blacklist/killlist
25 namespace
27 std::set<std::string> failed_plugins;
28 std::string killlist_file = "/killlist";
29 std::string blacklist_file = "/blacklist";
31 std::string string_add_list(std::string a, std::string b)
33 if(a.length())
34 return a + "," + b;
35 else
36 return b;
39 std::string get_name(std::string path)
41 #if defined(_WIN32) || defined(_WIN64)
42 const char* sep = "\\/";
43 #else
44 const char* sep = "/";
45 #endif
46 size_t p = path.find_last_of(sep);
47 std::string name;
48 if(p == std::string::npos)
49 name = path;
50 else
51 name = path.substr(p + 1);
52 return name;
55 std::string strip_extension(std::string tmp, std::string ext)
57 regex_results r = regex("(.*)\\." + ext, tmp);
58 if(!r) return tmp;
59 return r[1];
63 class wxeditor_plugins : public wxDialog
65 public:
66 wxeditor_plugins(wxWindow* parent);
67 void on_selection_change(wxCommandEvent& e);
68 void on_add(wxCommandEvent& e);
69 void on_rename(wxCommandEvent& e);
70 void on_enable(wxCommandEvent& e);
71 void on_delete(wxCommandEvent& e);
72 void on_start(wxCommandEvent& e);
73 void on_close(wxCommandEvent& e);
74 private:
75 void reload_plugins();
76 filelist& get_blacklist();
77 filelist& get_killlist();
78 wxListBox* plugins;
79 wxButton* addbutton;
80 wxButton* renamebutton;
81 wxButton* enablebutton;
82 wxButton* deletebutton;
83 wxButton* startbutton;
84 wxButton* closebutton;
85 std::vector<std::pair<std::string, bool>> pluginstbl;
86 std::string extension;
87 std::string pathpfx;
90 wxeditor_plugins::wxeditor_plugins(wxWindow* parent)
91 : wxDialog(parent, wxID_ANY, wxT("lsnes: Plugin manager"), wxDefaultPosition, wxSize(-1, -1))
93 CHECK_UI_THREAD;
94 Center();
95 wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0);
96 SetSizer(top_s);
97 pathpfx = get_config_path() + "/autoload";
98 extension = loadlib::library::extension();
100 top_s->Add(plugins = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(400, 300)), 1, wxGROW);
101 plugins->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
102 wxCommandEventHandler(wxeditor_plugins::on_selection_change), NULL, this);
104 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
105 pbutton_s->Add(addbutton = new wxButton(this, wxID_ANY, wxT("Add")), 0, wxGROW);
106 pbutton_s->Add(renamebutton = new wxButton(this, wxID_ANY, wxT("Rename")), 0, wxGROW);
107 pbutton_s->Add(enablebutton = new wxButton(this, wxID_ANY, wxT("Enable")), 0, wxGROW);
108 pbutton_s->Add(deletebutton = new wxButton(this, wxID_ANY, wxT("Delete")), 0, wxGROW);
109 pbutton_s->AddStretchSpacer();
110 if(!parent)
111 pbutton_s->Add(startbutton = new wxButton(this, wxID_ANY, wxT("Start")), 0, wxGROW);
112 else
113 startbutton = NULL;
114 if(!parent)
115 pbutton_s->Add(closebutton = new wxButton(this, wxID_EXIT, wxT("Quit")), 0, wxGROW);
116 else
117 pbutton_s->Add(closebutton = new wxButton(this, wxID_ANY, wxT("Close")), 0, wxGROW);
118 addbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_add), NULL,
119 this);
120 renamebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_rename), NULL,
121 this);
122 enablebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_enable), NULL,
123 this);
124 deletebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_delete), NULL,
125 this);
126 if(startbutton)
127 startbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_start),
128 NULL, this);
129 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_plugins::on_close), NULL,
130 this);
131 top_s->Add(pbutton_s, 0, wxGROW);
132 reload_plugins();
133 wxCommandEvent e;
134 on_selection_change(e);
135 Fit();
138 void wxeditor_plugins::reload_plugins()
140 CHECK_UI_THREAD;
141 int sel = plugins->GetSelection();
142 std::string name;
143 if(sel == wxNOT_FOUND || sel >= (ssize_t)pluginstbl.size())
144 name = "";
145 else
146 name = pluginstbl[sel].first;
148 if(!directory::ensure_exists(pathpfx)) {
149 throw std::runtime_error("Can't create plugins directory");
151 auto dir = directory::enumerate(pathpfx, ".*\\." + extension);
152 plugins->Clear();
153 pluginstbl.clear();
154 for(auto i : dir) {
155 std::string name = get_name(i);
156 regex_results r = regex("(.*)\\." + extension, name);
157 if(!r) continue;
158 //Blacklisted plugins should be listed as disabled. Killlisted plugins shouldn't appear at all.
159 bool is_disabled = false;
160 auto blacklist = get_blacklist().enumerate();
161 auto killlist = get_killlist().enumerate();
162 if(killlist.count(name))
163 continue;
164 is_disabled = blacklist.count(name);
165 pluginstbl.push_back(std::make_pair(r[1], !is_disabled));
166 std::string r1 = r[1];
167 std::string attributes;
168 if(is_disabled) attributes = string_add_list(attributes, "disabled");
169 if(failed_plugins.count(r[1] + "." + extension)) attributes = string_add_list(attributes, "failed");
170 if(attributes.length()) attributes = " (" + attributes + ")";
171 plugins->Append(towxstring(r1 + attributes));
174 for(size_t i = 0; i < pluginstbl.size(); i++) {
175 if(pluginstbl[i].first == name)
176 plugins->SetSelection(i);
178 wxCommandEvent e;
179 on_selection_change(e);
182 void wxeditor_plugins::on_selection_change(wxCommandEvent& e)
184 CHECK_UI_THREAD;
185 int sel = plugins->GetSelection();
186 if(sel == wxNOT_FOUND || sel >= (ssize_t)pluginstbl.size()) {
187 renamebutton->Enable(false);
188 enablebutton->Enable(false);
189 deletebutton->Enable(false);
190 } else {
191 enablebutton->SetLabel(towxstring(pluginstbl[sel].second ? "Disable" : "Enable"));
192 renamebutton->Enable(true);
193 enablebutton->Enable(true);
194 deletebutton->Enable(true);
198 void wxeditor_plugins::on_add(wxCommandEvent& e)
200 CHECK_UI_THREAD;
201 try {
202 std::string file = choose_file_load(this, "Choose plugin to add", ".",
203 single_type(loadlib::library::extension(), loadlib::library::name()));
204 std::string name = strip_extension(get_name(file), extension);
205 std::string nname = pathpfx + "/" + name + "." + extension;
206 bool overwrite_ok = false;
207 bool first = true;
208 int counter = 2;
209 while(!overwrite_ok && directory::exists(nname)) {
210 if(first) {
211 wxMessageDialog* d3 = new wxMessageDialog(this,
212 towxstring("Plugin '" + name + "' already exists.\n\nOverwrite?"),
213 towxstring("Plugin already exists"),
214 wxYES_NO | wxCANCEL | wxNO_DEFAULT | wxICON_QUESTION);
215 int r = d3->ShowModal();
216 d3->Destroy();
217 first = false;
218 if(r == wxID_YES)
219 break;
220 if(r == wxID_CANCEL) {
221 reload_plugins();
222 return;
225 nname = pathpfx + "/" + name + "(" + (stringfmt() << counter++).str() + ")." + extension;
227 std::string nnamet = nname + ".tmp";
228 std::ifstream in(file, std::ios::binary);
229 std::ofstream out(nnamet, std::ios::binary);
230 if(!out) {
231 show_message_ok(this, "Error", "Can't write file '" + nnamet + "'", wxICON_EXCLAMATION);
232 reload_plugins();
233 return;
235 if(!in) {
236 remove(nnamet.c_str());
237 show_message_ok(this, "Error", "Can't read file '" + file + "'", wxICON_EXCLAMATION);
238 reload_plugins();
239 return;
241 while(true) {
242 char buf[4096];
243 size_t r;
244 r = in.readsome(buf, sizeof(buf));
245 out.write(buf, r);
246 if(!r)
247 break;
249 if(!out) {
250 remove(nnamet.c_str());
251 show_message_ok(this, "Error", "Can't write file '" + nnamet + "'", wxICON_EXCLAMATION);
252 reload_plugins();
253 return;
255 //Set permissions.
256 #if defined(_WIN32) || defined(_WIN64)
257 #else
258 struct stat s;
259 if(stat(file.c_str(), &s) < 0)
260 s.st_mode = 0644;
261 if(s.st_mode & 0400) s.st_mode |= 0100;
262 if(s.st_mode & 040) s.st_mode |= 010;
263 if(s.st_mode & 04) s.st_mode |= 01;
264 chmod(nnamet.c_str(), s.st_mode & 0777);
265 #endif
266 if(directory::rename_overwrite(nnamet.c_str(), nname.c_str())) {
267 remove(nnamet.c_str());
268 show_message_ok(this, "Error", "Can't rename-over file '" + nname + "'",
269 wxICON_EXCLAMATION);
270 reload_plugins();
271 return;
273 //The new plugin isn't failed.
274 failed_plugins.erase(get_name(nname));
275 //Nor is it on killlist/blacklist
276 try { get_blacklist().remove(get_name(nname)); } catch(...) {}
277 try { get_killlist().remove(get_name(nname)); } catch(...) {}
278 reload_plugins();
279 } catch(canceled_exception& e) {
283 void wxeditor_plugins::on_rename(wxCommandEvent& e)
285 CHECK_UI_THREAD;
286 int sel = plugins->GetSelection();
287 if(sel == wxNOT_FOUND || sel >= (ssize_t)pluginstbl.size())
288 return;
289 std::string name = pluginstbl[sel].first;
290 std::string name2;
291 try {
292 name2 = pick_text(this, "Rename plugin to", "Enter new name for plugin", name, false);
293 } catch(canceled_exception& e) {
294 return;
296 std::string oname = pathpfx + "/" + name + "." + extension;
297 std::string nname = pathpfx + "/" + name2 + "." + extension;
298 if(oname != nname) {
299 directory::rename_overwrite(oname.c_str(), nname.c_str());
301 pluginstbl[sel].first = name2;
302 if(failed_plugins.count(name + "." + extension)) {
303 failed_plugins.insert(name2 + "." + extension);
304 failed_plugins.erase(name + "." + extension);
305 } else
306 failed_plugins.erase(name2 + "." + extension);
307 try { get_blacklist().rename(name + "." + extension, name2 + "." + extension); } catch(...) {}
308 reload_plugins();
311 void wxeditor_plugins::on_enable(wxCommandEvent& e)
313 CHECK_UI_THREAD;
314 int sel = plugins->GetSelection();
315 if(sel == wxNOT_FOUND || sel >= (ssize_t)pluginstbl.size())
316 return;
317 try {
318 if(pluginstbl[sel].second)
319 get_blacklist().add(pluginstbl[sel].first + "." + extension);
320 else
321 get_blacklist().remove(pluginstbl[sel].first + "." + extension);
322 } catch(std::exception& e) {
323 show_message_ok(this, "Error", "Can't enable/disable plugin '" + pluginstbl[sel].first +
324 "': " + e.what(), wxICON_EXCLAMATION);
325 reload_plugins();
326 return;
328 pluginstbl[sel].second = !pluginstbl[sel].second;
329 reload_plugins();
332 void wxeditor_plugins::on_delete(wxCommandEvent& e)
334 CHECK_UI_THREAD;
335 int sel = plugins->GetSelection();
336 if(sel == wxNOT_FOUND || sel >= (ssize_t)pluginstbl.size())
337 return;
338 std::string oname = pathpfx + "/" + pluginstbl[sel].first + "." + extension;
339 if(remove(oname.c_str()) < 0) {
340 //Killlist it then.
341 try { get_killlist().add(pluginstbl[sel].first + "." + extension); } catch(...) {}
343 failed_plugins.erase(pluginstbl[sel].first + "." + extension);
344 reload_plugins();
347 void wxeditor_plugins::on_start(wxCommandEvent& e)
349 CHECK_UI_THREAD;
350 EndModal(wxID_OK);
353 void wxeditor_plugins::on_close(wxCommandEvent& e)
355 CHECK_UI_THREAD;
356 EndModal(wxID_CANCEL);
359 filelist& wxeditor_plugins::get_blacklist()
361 static filelist x(pathpfx + blacklist_file, pathpfx);
362 return x;
365 filelist& wxeditor_plugins::get_killlist()
367 static filelist x(pathpfx + killlist_file, pathpfx);
368 return x;
371 bool wxeditor_plugin_manager_display(wxWindow* parent)
373 CHECK_UI_THREAD;
374 int r;
375 modal_pause_holder* hld = NULL;
376 try {
377 if(parent)
378 hld = new modal_pause_holder();
379 wxDialog* editor;
380 try {
381 editor = new wxeditor_plugins(parent);
382 r = editor->ShowModal();
383 } catch(std::exception& e) {
384 if(hld)
385 messages << "Error opening plugin dialog: " << e.what() << std::endl;
386 else
387 std::cerr << "Error opening plugin dialog: " << e.what() << std::endl;
388 if(hld) delete hld;
389 return false;
390 } catch(...) {
391 if(hld) delete hld;
392 return false;
394 editor->Destroy();
395 if(hld) delete hld;
396 return (r == wxID_OK);
397 } catch(...) {
398 if(hld) delete hld;
399 return false;
403 void wxeditor_plugin_manager_notify_fail(const std::string& libname)
405 failed_plugins.insert(libname);