Refactor library zip functions to dedicated namespace
[lsnes.git] / src / platform / wxwidgets / main.cpp
blobe2293a4def30d9f405569547765e9fd4fa471740
1 #include <wx/wx.h>
3 #include "lsnes.hpp"
5 #include "core/command.hpp"
6 #include "core/controller.hpp"
7 #include "core/dispatch.hpp"
8 #include "core/framerate.hpp"
9 #include "core/joystickapi.hpp"
10 #include "core/keymapper.hpp"
11 #include "core/loadlib.hpp"
12 #include "lua/lua.hpp"
13 #include "core/mainloop.hpp"
14 #include "core/misc.hpp"
15 #include "core/moviedata.hpp"
16 #include "core/rom.hpp"
17 #include "core/rrdata.hpp"
18 #include "core/settings.hpp"
19 #include "core/window.hpp"
20 #include "interface/romtype.hpp"
21 #include "library/string.hpp"
22 #include "library/threadtypes.hpp"
23 #include "library/utf8.hpp"
24 #include "library/zip.hpp"
26 #include "platform/wxwidgets/settings-common.hpp"
27 #include "platform/wxwidgets/platform.hpp"
28 #include "platform/wxwidgets/window_messages.hpp"
29 #include "platform/wxwidgets/window_status.hpp"
30 #include "platform/wxwidgets/window_mainwindow.hpp"
32 #include <cassert>
33 #include <boost/lexical_cast.hpp>
35 #include <wx/wx.h>
36 #include <wx/event.h>
37 #include <wx/control.h>
38 #include <wx/combobox.h>
39 #include <wx/cmdline.h>
40 #include <iostream>
42 #define UISERV_REFRESH_TITLE 9990
43 #define UISERV_RESIZED 9991
44 #define UISERV_UIFUN 9992
45 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
46 #define UISERV_EXIT 9994
47 #define UISERV_UPDATE_STATUS 9995
48 #define UISERV_UPDATE_MESSAGES 9996
49 #define UISERV_UPDATE_SCREEN 9997
50 #define UISERV_PANIC 9998
51 #define UISERV_ERROR 9999
53 wxwin_messages* msg_window;
54 wxwin_mainwindow* main_window;
55 std::string our_rom_name;
57 bool wxwidgets_exiting = false;
59 namespace
61 threadid_class ui_thread;
62 volatile bool panic_ack = false;
63 std::string error_message_text;
64 volatile bool modal_dialog_confirm;
65 volatile bool modal_dialog_active;
66 mutex_class ui_mutex;
67 cv_class ui_condition;
68 thread_class* joystick_thread_handle;
70 void* joystick_thread(int _args)
72 joystick_driver_thread_fn();
73 return NULL;
76 struct uiserv_event : public wxEvent
78 uiserv_event(int code)
80 SetId(code);
83 wxEvent* Clone() const
85 return new uiserv_event(*this);
89 class ui_services_type : public wxEvtHandler
91 bool ProcessEvent(wxEvent& event);
94 struct ui_queue_entry
96 void(*fn)(void*);
97 void* arg;
100 std::list<ui_queue_entry> ui_queue;
102 bool ui_services_type::ProcessEvent(wxEvent& event)
104 int c = event.GetId();
105 if(c == UISERV_PANIC) {
106 //We need to panic.
107 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR |
108 wxOK);
109 panic_ack = true;
110 } else if(c == UISERV_REFRESH_TITLE) {
111 if(main_window)
112 main_window->refresh_title();
113 } else if(c == UISERV_RESIZED) {
114 if(main_window)
115 main_window->notify_resized();
116 } else if(c == UISERV_ERROR) {
117 std::string text = error_message_text;
118 wxMessageBox(towxstring(text), _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, main_window);
119 } else if(c == UISERV_UPDATE_MESSAGES) {
120 if(msg_window)
121 msg_window->notify_update();
122 } else if(c == UISERV_UPDATE_STATUS) {
123 if(main_window)
124 main_window->notify_update_status();
125 wxeditor_movie_update();
126 wxeditor_hexeditor_update();
127 } else if(c == UISERV_UPDATE_SCREEN) {
128 if(main_window)
129 main_window->notify_update();
130 wxwindow_memorysearch_update();
131 wxwindow_tasinput_update();
132 } else if(c == UISERV_EXIT) {
133 if(main_window)
134 main_window->notify_exit();
135 } else if(c == UISERV_UIFUN) {
136 std::list<ui_queue_entry>::iterator i;
137 ui_queue_entry e;
138 queue_synchronous_fn_warning = true;
139 back:
141 umutex_class h(ui_mutex);
142 if(ui_queue.empty())
143 goto end;
144 i = ui_queue.begin();
145 e = *i;
146 ui_queue.erase(i);
148 e.fn(e.arg);
149 goto back;
150 end:
151 queue_synchronous_fn_warning = false;
153 return true;
156 ui_services_type* ui_services;
158 void post_ui_event(int code)
160 uiserv_event uic(code);
161 wxPostEvent(ui_services, uic);
164 void handle_config_line(std::string line)
166 regex_results r;
167 if(r = regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
168 lsnes_vsetc.set(r[1], r[2], true);
169 messages << "Setting " << r[1] << " set to " << r[2] << std::endl;
170 } else if(r = regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
171 if(!lsnes_cmd.valid_alias_name(r[1])) {
172 messages << "Illegal alias name " << r[1] << std::endl;
173 return;
175 std::string tmp = lsnes_cmd.get_alias_for(r[1]);
176 tmp = tmp + r[2] + "\n";
177 lsnes_cmd.set_alias_for(r[1], tmp);
178 messages << r[1] << " aliased to " << r[2] << std::endl;
179 } else if(r = regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line)) {
180 lsnes_mapper.bind(r[1], r[2], r[3], r[4]);
181 if(r[1] != "" || r[2] != "")
182 messages << r[1] << "/" << r[2] << " ";
183 messages << r[3] << " bound to '" << r[4] << "'" << std::endl;
184 } else if(r = regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line)) {
185 keyboard::ctrlrkey* ckey = lsnes_mapper.get_controllerkey(r[2]);
186 if(ckey) {
187 ckey->append(r[1]);
188 messages << r[1] << " bound (button) to " << r[2] << std::endl;
189 } else
190 button_keys[r[2]] = r[1];
191 } else if(r = regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
192 if(r[2] != "") {
193 core_selections[r[1]] = r[2];
194 messages << "Prefer " << r[2] << " for " << r[1] << std::endl;
196 } else
197 (stringfmt() << "Unrecognized directive: " << line).throwex();
200 void load_configuration()
202 std::string cfg = get_config_path() + "/lsneswxw.cfg";
203 std::ifstream cfgfile(cfg.c_str());
204 std::string line;
205 size_t lineno = 1;
206 while(std::getline(cfgfile, line)) {
207 try {
208 handle_config_line(line);
209 } catch(std::exception& e) {
210 messages << "Error processing line " << lineno << ": " << e.what() << std::endl;
212 lineno++;
214 refresh_alias_binds();
217 void save_configuration()
219 std::string cfg = get_config_path() + "/lsneswxw.cfg";
220 std::ofstream cfgfile(cfg.c_str());
221 //Settings.
222 for(auto i : lsnes_vsetc.get_all())
223 cfgfile << "SET " << i.first << " " << i.second << std::endl;
224 //Aliases.
225 for(auto i : lsnes_cmd.get_aliases()) {
226 std::string old_alias_value = lsnes_cmd.get_alias_for(i);
227 while(old_alias_value != "") {
228 std::string aliasline;
229 size_t s = old_alias_value.find_first_of("\n");
230 if(s < old_alias_value.length()) {
231 aliasline = old_alias_value.substr(0, s);
232 old_alias_value = old_alias_value.substr(s + 1);
233 } else {
234 aliasline = old_alias_value;
235 old_alias_value = "";
237 cfgfile << "ALIAS " << i << " " << aliasline << std::endl;
240 //Keybindings.
241 for(auto i : lsnes_mapper.get_bindings())
242 cfgfile << "BIND " << std::string(i) << " " << lsnes_mapper.get(i) << std::endl;
243 //Buttons.
244 for(auto i : lsnes_mapper.get_controller_keys()) {
245 std::string b;
246 unsigned idx = 0;
247 while((b = i->get_string(idx++)) != "")
248 cfgfile << "BUTTON " << b << " " << i->get_command() << std::endl;
250 for(auto i : button_keys)
251 cfgfile << "BUTTON " << i.second << " " << i.first << std::endl;
252 for(auto i : core_selections)
253 if(i.second != "")
254 cfgfile << "PREFER " << i.first << " " << i.second << std::endl;
255 //Last save.
256 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
257 lsave << last_save;
261 void* eloop_helper(int x)
263 platform::dummy_event_loop();
264 return NULL;
267 std::string get_loaded_movie(const std::vector<std::string>& cmdline)
269 for(auto i : cmdline)
270 if(!i.empty() && i[0] != '-')
271 return i;
272 return "";
276 wxString towxstring(const std::string& str) throw(std::bad_alloc)
278 return wxString(str.c_str(), wxConvUTF8);
281 std::string tostdstring(const wxString& str) throw(std::bad_alloc)
283 return std::string(str.mb_str(wxConvUTF8));
286 wxString towxstring(const std::u32string& str) throw(std::bad_alloc)
288 return wxString(to_u8string(str).c_str(), wxConvUTF8);
291 std::u32string tou32string(const wxString& str) throw(std::bad_alloc)
293 return to_u32string(std::string(str.mb_str(wxConvUTF8)));
296 std::string pick_archive_member(wxWindow* parent, const std::string& filename) throw(std::bad_alloc)
298 //Did we pick a .zip file?
299 std::string f;
300 try {
301 zip::reader zr(filename);
302 std::vector<wxString> files;
303 for(auto i : zr)
304 files.push_back(towxstring(i));
305 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
306 wxT("Select member"), files.size(), &files[0]);
307 if(d2->ShowModal() == wxID_CANCEL) {
308 d2->Destroy();
309 return "";
311 f = filename + "/" + tostdstring(d2->GetStringSelection());
312 d2->Destroy();
313 } catch(...) {
314 //Ignore error.
315 f = filename;
317 return f;
320 void signal_program_exit()
322 post_ui_event(UISERV_EXIT);
325 void signal_resize_needed()
327 post_ui_event(UISERV_RESIZED);
331 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
332 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
333 wxCMD_LINE_PARAM_MULTIPLE },
334 { wxCMD_LINE_NONE }
337 class lsnes_app : public wxApp
339 public:
340 lsnes_app();
341 virtual bool OnInit();
342 virtual int OnExit();
343 virtual void OnInitCmdLine(wxCmdLineParser& parser);
344 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
345 private:
346 bool settings_mode;
347 std::string c_rom;
348 std::string c_file;
349 std::vector<std::string> cmdline;
350 std::map<std::string, std::string> c_settings;
351 std::vector<std::string> c_lua;
352 bool exit_immediately;
355 IMPLEMENT_APP(lsnes_app)
357 lsnes_app::lsnes_app()
359 settings_mode = false;
360 exit_immediately = false;
363 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
365 parser.SetDesc(dummy_descriptor_table);
366 parser.SetSwitchChars(wxT(""));
369 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
371 for(size_t i = 0; i< parser.GetParamCount(); i++)
372 cmdline.push_back(tostdstring(parser.GetParam(i)));
373 for(auto i: cmdline) {
374 regex_results r;
375 if(i == "--help" || i == "-h") {
376 std::cout << "--settings: Show the settings dialog" << std::endl;
377 std::cout << "--rom=<filename>: Load specified ROM on startup" << std::endl;
378 std::cout << "--load=<filename>: Load specified save/movie on starup" << std::endl;
379 std::cout << "--lua=<filename>: Load specified Lua script on startup" << std::endl;
380 std::cout << "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl;
381 std::cout << "<filename>: Load specified ROM on startup" << std::endl;
382 exit_immediately = true;
383 return true;
385 if(i == "--settings")
386 settings_mode = true;
387 if(r = regex("--set=([^=]+)=(.+)", i))
388 c_settings[r[1]] = r[2];
389 if(r = regex("--lua=(.+)", i))
390 c_lua.push_back(r[1]);
392 return true;
396 bool lsnes_app::OnInit()
398 wxApp::OnInit();
399 if(exit_immediately)
400 return false;
402 reached_main();
403 set_random_seed();
404 bring_app_foreground();
406 ui_services = new ui_services_type();
408 ui_thread = this_thread_id();
409 platform::init();
411 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
413 loaded_rom dummy_rom;
414 std::map<std::string, std::string> settings;
415 auto ctrldata = dummy_rom.rtype->controllerconfig(settings);
416 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
418 reinitialize_buttonmap();
419 controls.set_ports(ports);
421 std::string cfgpath = get_config_path();
422 autoload_libraries();
423 messages << "Saving per-user data to: " << get_config_path() << std::endl;
424 messages << "--- Loading configuration --- " << std::endl;
425 load_configuration();
426 messages << "--- End running lsnesrc --- " << std::endl;
428 if(settings_mode) {
429 //We got to boot this up quite a bit to get the joystick driver working.
430 //In practicular, we need joystick thread and emulator thread in pause.
431 joystick_thread_handle = new thread_class(joystick_thread, 6);
432 thread_class* dummy_loop = new thread_class(eloop_helper, 8);
433 display_settings_dialog(NULL, NULL);
434 platform::exit_dummy_event_loop();
435 joystick_driver_signal();
436 joystick_thread_handle->join();
437 dummy_loop->join();
438 save_configuration();
439 return false;
441 init_lua();
443 joystick_thread_handle = new thread_class(joystick_thread, 7);
445 msg_window = new wxwin_messages();
446 msg_window->Show();
448 const std::string movie_file = get_loaded_movie(cmdline);
449 loaded_rom rom;
450 try {
451 moviefile mov;
452 rom = construct_rom(movie_file, cmdline);
453 rom.load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
454 } catch(std::exception& e) {
455 std::cerr << "Can't load ROM: " << e.what() << std::endl;
456 return false;
459 moviefile* mov = NULL;
460 if(movie_file != "")
461 try {
462 mov = new moviefile(movie_file, *rom.rtype);
463 rom.load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
464 } catch(std::exception& e) {
465 std::cerr << "Can't load state: " << e.what() << std::endl;
466 return false;
468 else {
469 mov = new moviefile;
470 mov->settings = c_settings;
471 auto ctrldata = rom.rtype->controllerconfig(mov->settings);
472 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
473 mov->input.clear(ports);
474 mov->coreversion = rom.rtype->get_core_identifier();
475 mov->projectid = get_random_hexstring(40);
476 if(!rom.rtype->isnull()) {
477 //Initialize the remainder.
478 mov->rerecords = "0";
479 for(size_t i = 0; i < ROM_SLOT_COUNT; i++) {
480 mov->romimg_sha256[i] = rom.romimg[i].sha_256.read();
481 mov->romxml_sha256[i] = rom.romxml[i].sha_256.read();
482 mov->namehint[i] = rom.romimg[i].namehint;
485 mov->gametype = &rom.rtype->combine_region(*rom.region);
487 our_rom = rom;
488 mov->start_paused = true;
489 for(auto i : c_lua) {
490 messages << "Trying to run Lua script: " << i << std::endl;
491 lsnes_cmd.invoke("run-lua " + i);
493 boot_emulator(rom, *mov);
494 return true;
497 int lsnes_app::OnExit()
499 if(settings_mode)
500 return 0;
501 //NULL these so no further messages will be sent.
502 auto x = msg_window;
503 msg_window = NULL;
504 main_window = NULL;
505 if(x)
506 x->Destroy();
507 save_configuration();
508 information_dispatch::do_dump_end();
509 rrdata.close();
510 quit_lua();
511 joystick_driver_signal();
512 joystick_thread_handle->join();
513 platform::quit();
514 cleanup_all_keys();
515 cleanup_keymapper();
516 return 0;
519 namespace
521 struct _graphics_driver drv = {
522 .init = []() -> void {
523 initialize_wx_keyboard();
525 .quit = []() -> void {},
526 .notify_message = []() -> void
528 post_ui_event(UISERV_UPDATE_MESSAGES);
530 .notify_status = []() -> void
532 post_ui_event(UISERV_UPDATE_STATUS);
534 .notify_screen = []() -> void
536 post_ui_event(UISERV_UPDATE_SCREEN);
538 .error_message = [](const std::string& text) -> void {
539 error_message_text = text;
540 post_ui_event(UISERV_ERROR);
542 .fatal_error = []() -> void {
543 //Fun: This can be called from any thread!
544 if(ui_thread == this_thread_id()) {
545 //UI thread.
546 platform::set_modal_pause(true);
547 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"),
548 wxICON_ERROR | wxOK);
549 } else {
550 //Emulation thread panic. Signal the UI thread.
551 post_ui_event(UISERV_PANIC);
552 while(!panic_ack);
555 .name = []() -> const char* { return "wxwidgets graphics plugin"; },
556 .action_updated = []()
558 runuifun([]() -> void { main_window->action_updated(); });
560 .request_rom = [](rom_request& req)
562 rom_request* _req = &req;
563 mutex_class lock;
564 cv_class cv;
565 bool done = false;
566 umutex_class h(lock);
567 runuifun([_req, &lock, &cv, &done]() -> void {
568 try {
569 main_window->request_rom(*_req);
570 } catch(...) {
571 _req->canceled = true;
573 umutex_class h(lock);
574 done = true;
575 cv.notify_all();
577 while(!done)
578 cv.wait(h);
581 struct graphics_driver _drv(drv);
584 void signal_core_change()
586 post_ui_event(UISERV_REFRESH_TITLE);
589 void _runuifun_async(void (*fn)(void*), void* arg)
591 umutex_class h(ui_mutex);
592 ui_queue_entry e;
593 e.fn = fn;
594 e.arg = arg;
595 ui_queue.push_back(e);
596 post_ui_event(UISERV_UIFUN);
600 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
602 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir)
604 wxString _title = towxstring(title);
605 wxString _startdir = towxstring(startdir);
606 std::string filespec;
607 filespec = "All files|*";
608 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), towxstring(filespec), wxFD_OPEN);
609 if(d->ShowModal() == wxID_CANCEL)
610 throw canceled_exception();
611 std::string filename = tostdstring(d->GetPath());
612 d->Destroy();
613 if(filename == "")
614 throw canceled_exception();
615 return filename;
618 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
620 std::string filename = pick_file(parent, title, startdir);
621 //Did we pick a .zip file?
622 if(!regex_match(".*\\.[zZ][iI][pP]", filename))
623 return filename; //Not a ZIP.
624 try {
625 zip::reader zr(filename);
626 std::vector<std::string> files;
627 for(auto i : zr)
628 files.push_back(i);
629 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
630 } catch(canceled_exception& e) {
631 //Throw these forward.
632 throw;
633 } catch(...) {
634 //Ignore error.
636 return filename;
639 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
640 const std::vector<std::string>& choices, unsigned defaultchoice)
642 std::vector<wxString> _choices;
643 for(auto i : choices)
644 _choices.push_back(towxstring(i));
645 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
646 _choices.size(), &_choices[0]);
647 d2->SetSelection(defaultchoice);
648 if(d2->ShowModal() == wxID_CANCEL) {
649 d2->Destroy();
650 throw canceled_exception();
652 std::string out = tostdstring(d2->GetStringSelection());
653 d2->Destroy();
654 return out;
657 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
658 bool multiline)
660 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
661 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
662 if(d2->ShowModal() == wxID_CANCEL) {
663 d2->Destroy();
664 throw canceled_exception();
666 std::string text = tostdstring(d2->GetValue());
667 d2->Destroy();
668 return text;
671 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
673 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
674 d3->ShowModal();
675 d3->Destroy();