Fix some memory leak complaints from Valgrind
[lsnes.git] / src / platform / wxwidgets / main.cpp
blob4bdf3fc7da3f689db814bb812a415d2fc23e609a
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/movie.hpp"
16 #include "core/moviefile-common.hpp"
17 #include "core/moviedata.hpp"
18 #include "core/rom.hpp"
19 #include "core/settings.hpp"
20 #include "core/window.hpp"
21 #include "interface/romtype.hpp"
22 #include "library/string.hpp"
23 #include "library/threadtypes.hpp"
24 #include "library/utf8.hpp"
25 #include "library/zip.hpp"
27 #include "platform/wxwidgets/settings-common.hpp"
28 #include "platform/wxwidgets/platform.hpp"
29 #include "platform/wxwidgets/window_messages.hpp"
30 #include "platform/wxwidgets/window_status.hpp"
31 #include "platform/wxwidgets/window_mainwindow.hpp"
33 #include <cassert>
34 #include <boost/lexical_cast.hpp>
36 #include <wx/wx.h>
37 #include <wx/event.h>
38 #include <wx/control.h>
39 #include <wx/combobox.h>
40 #include <wx/cmdline.h>
41 #include <iostream>
43 #define UISERV_REFRESH_TITLE 9990
44 #define UISERV_RESIZED 9991
45 #define UISERV_UIFUN 9992
46 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
47 #define UISERV_EXIT 9994
48 #define UISERV_UPDATE_STATUS 9995
49 #define UISERV_UPDATE_MESSAGES 9996
50 #define UISERV_UPDATE_SCREEN 9997
51 #define UISERV_PANIC 9998
52 #define UISERV_ERROR 9999
54 wxwin_messages* msg_window;
55 wxwin_mainwindow* main_window;
56 std::string our_rom_name;
58 bool wxwidgets_exiting = false;
60 namespace
62 threadid_class ui_thread;
63 volatile bool panic_ack = false;
64 std::string error_message_text;
65 volatile bool modal_dialog_confirm;
66 volatile bool modal_dialog_active;
67 mutex_class ui_mutex;
68 cv_class ui_condition;
69 thread_class* joystick_thread_handle;
71 void* joystick_thread(int _args)
73 joystick_driver_thread_fn();
74 return NULL;
77 struct uiserv_event : public wxEvent
79 uiserv_event(int code)
81 SetId(code);
84 wxEvent* Clone() const
86 return new uiserv_event(*this);
90 class ui_services_type : public wxEvtHandler
92 bool ProcessEvent(wxEvent& event);
95 struct ui_queue_entry
97 void(*fn)(void*);
98 void* arg;
101 std::list<ui_queue_entry> ui_queue;
103 bool ui_services_type::ProcessEvent(wxEvent& event)
105 int c = event.GetId();
106 if(c == UISERV_PANIC) {
107 //We need to panic.
108 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR |
109 wxOK);
110 panic_ack = true;
111 } else if(c == UISERV_REFRESH_TITLE) {
112 if(main_window)
113 main_window->refresh_title();
114 } else if(c == UISERV_RESIZED) {
115 if(main_window)
116 main_window->notify_resized();
117 } else if(c == UISERV_ERROR) {
118 std::string text = error_message_text;
119 wxMessageBox(towxstring(text), _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, main_window);
120 } else if(c == UISERV_UPDATE_MESSAGES) {
121 if(msg_window)
122 msg_window->notify_update();
123 } else if(c == UISERV_UPDATE_STATUS) {
124 if(main_window)
125 main_window->notify_update_status();
126 wxeditor_movie_update();
127 wxeditor_hexeditor_update();
128 } else if(c == UISERV_UPDATE_SCREEN) {
129 if(main_window)
130 main_window->notify_update();
131 wxwindow_memorysearch_update();
132 wxwindow_tasinput_update();
133 } else if(c == UISERV_EXIT) {
134 if(main_window)
135 main_window->notify_exit();
136 } else if(c == UISERV_UIFUN) {
137 std::list<ui_queue_entry>::iterator i;
138 ui_queue_entry e;
139 queue_synchronous_fn_warning = true;
140 back:
142 umutex_class h(ui_mutex);
143 if(ui_queue.empty())
144 goto end;
145 i = ui_queue.begin();
146 e = *i;
147 ui_queue.erase(i);
149 e.fn(e.arg);
150 goto back;
151 end:
152 queue_synchronous_fn_warning = false;
154 return true;
157 ui_services_type* ui_services;
159 void post_ui_event(int code)
161 uiserv_event uic(code);
162 wxPostEvent(ui_services, uic);
165 void handle_config_line(std::string line)
167 regex_results r;
168 if(r = regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
169 lsnes_vsetc.set(r[1], r[2], true);
170 messages << "Setting " << r[1] << " set to " << r[2] << std::endl;
171 } else if(r = regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
172 if(!lsnes_cmd.valid_alias_name(r[1])) {
173 messages << "Illegal alias name " << r[1] << std::endl;
174 return;
176 std::string tmp = lsnes_cmd.get_alias_for(r[1]);
177 tmp = tmp + r[2] + "\n";
178 lsnes_cmd.set_alias_for(r[1], tmp);
179 messages << r[1] << " aliased to " << r[2] << std::endl;
180 } else if(r = regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line)) {
181 std::string tmp = r[4];
182 regex_results r2 = regex("(load|load-smart|load-readonly|load-preserve|load-state"
183 "|load-movie|save-state|save-movie)[ \t]+\\$\\{project\\}(.*)\\.lsmv", tmp);
184 if(r2) tmp = r2[1] + " $SLOT:" + r2[2];
185 lsnes_mapper.bind(r[1], r[2], r[3], tmp);
186 if(r[1] != "" || r[2] != "")
187 messages << r[1] << "/" << r[2] << " ";
188 messages << r[3] << " bound to '" << tmp << "'" << std::endl;
189 } else if(r = regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line)) {
190 keyboard::ctrlrkey* ckey = lsnes_mapper.get_controllerkey(r[2]);
191 if(ckey) {
192 ckey->append(r[1]);
193 messages << r[1] << " bound (button) to " << r[2] << std::endl;
194 } else
195 button_keys[r[2]] = r[1];
196 } else if(r = regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
197 if(r[2] != "") {
198 core_selections[r[1]] = r[2];
199 messages << "Prefer " << r[2] << " for " << r[1] << std::endl;
201 } else
202 (stringfmt() << "Unrecognized directive: " << line).throwex();
205 void load_configuration()
207 std::string cfg = get_config_path() + "/lsneswxw.cfg";
208 std::ifstream cfgfile(cfg.c_str());
209 std::string line;
210 size_t lineno = 1;
211 while(std::getline(cfgfile, line)) {
212 try {
213 handle_config_line(line);
214 } catch(std::exception& e) {
215 messages << "Error processing line " << lineno << ": " << e.what() << std::endl;
217 lineno++;
219 refresh_alias_binds();
222 void save_configuration()
224 std::string cfg = get_config_path() + "/lsneswxw.cfg";
225 std::ofstream cfgfile(cfg.c_str());
226 //Settings.
227 for(auto i : lsnes_vsetc.get_all())
228 cfgfile << "SET " << i.first << " " << i.second << std::endl;
229 //Aliases.
230 for(auto i : lsnes_cmd.get_aliases()) {
231 std::string old_alias_value = lsnes_cmd.get_alias_for(i);
232 while(old_alias_value != "") {
233 std::string aliasline;
234 size_t s = old_alias_value.find_first_of("\n");
235 if(s < old_alias_value.length()) {
236 aliasline = old_alias_value.substr(0, s);
237 old_alias_value = old_alias_value.substr(s + 1);
238 } else {
239 aliasline = old_alias_value;
240 old_alias_value = "";
242 cfgfile << "ALIAS " << i << " " << aliasline << std::endl;
245 //Keybindings.
246 for(auto i : lsnes_mapper.get_bindings())
247 cfgfile << "BIND " << std::string(i) << " " << lsnes_mapper.get(i) << std::endl;
248 //Buttons.
249 for(auto i : lsnes_mapper.get_controller_keys()) {
250 std::string b;
251 unsigned idx = 0;
252 while((b = i->get_string(idx++)) != "")
253 cfgfile << "BUTTON " << b << " " << i->get_command() << std::endl;
255 for(auto i : button_keys)
256 cfgfile << "BUTTON " << i.second << " " << i.first << std::endl;
257 for(auto i : core_selections)
258 if(i.second != "")
259 cfgfile << "PREFER " << i.first << " " << i.second << std::endl;
260 //Last save.
261 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
262 lsave << last_save;
266 void* eloop_helper(int x)
268 platform::dummy_event_loop();
269 return NULL;
272 std::string get_loaded_movie(const std::vector<std::string>& cmdline)
274 for(auto i : cmdline)
275 if(!i.empty() && i[0] != '-')
276 return i;
277 return "";
281 wxString towxstring(const std::string& str) throw(std::bad_alloc)
283 return wxString(str.c_str(), wxConvUTF8);
286 std::string tostdstring(const wxString& str) throw(std::bad_alloc)
288 return std::string(str.mb_str(wxConvUTF8));
291 wxString towxstring(const std::u32string& str) throw(std::bad_alloc)
293 return wxString(utf8::to8(str).c_str(), wxConvUTF8);
296 std::u32string tou32string(const wxString& str) throw(std::bad_alloc)
298 return utf8::to32(std::string(str.mb_str(wxConvUTF8)));
301 std::string pick_archive_member(wxWindow* parent, const std::string& filename) throw(std::bad_alloc)
303 //Did we pick a .zip file?
304 std::string f;
305 try {
306 zip::reader zr(filename);
307 std::vector<wxString> files;
308 for(auto i : zr)
309 files.push_back(towxstring(i));
310 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
311 wxT("Select member"), files.size(), &files[0]);
312 if(d2->ShowModal() == wxID_CANCEL) {
313 d2->Destroy();
314 return "";
316 f = filename + "/" + tostdstring(d2->GetStringSelection());
317 d2->Destroy();
318 } catch(...) {
319 //Ignore error.
320 f = filename;
322 return f;
325 void signal_program_exit()
327 post_ui_event(UISERV_EXIT);
330 void signal_resize_needed()
332 post_ui_event(UISERV_RESIZED);
336 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
337 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
338 wxCMD_LINE_PARAM_MULTIPLE },
339 { wxCMD_LINE_NONE }
342 class lsnes_app : public wxApp
344 public:
345 lsnes_app();
346 virtual bool OnInit();
347 virtual int OnExit();
348 virtual void OnInitCmdLine(wxCmdLineParser& parser);
349 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
350 private:
351 bool settings_mode;
352 bool pluginmanager_mode;
353 std::string c_rom;
354 std::string c_file;
355 std::vector<std::string> cmdline;
356 std::map<std::string, std::string> c_settings;
357 std::vector<std::string> c_lua;
358 bool exit_immediately;
359 bool fullscreen_mode;
360 bool start_unpaused;
363 IMPLEMENT_APP(lsnes_app)
365 lsnes_app::lsnes_app()
367 settings_mode = false;
368 pluginmanager_mode = false;
369 exit_immediately = false;
370 fullscreen_mode = false;
371 start_unpaused = false;
374 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
376 parser.SetDesc(dummy_descriptor_table);
377 parser.SetSwitchChars(wxT(""));
380 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
382 for(size_t i = 0; i< parser.GetParamCount(); i++)
383 cmdline.push_back(tostdstring(parser.GetParam(i)));
384 for(auto i: cmdline) {
385 regex_results r;
386 if(i == "--help" || i == "-h") {
387 std::cout << "--settings: Show the settings dialog" << std::endl;
388 std::cout << "--pluginmanager: Show the plugin manager" << std::endl;
389 std::cout << "--fullscreen: Start fullscreen" << std::endl;
390 std::cout << "--unpause: Start unpaused (only if ROM is loaded)" << std::endl;
391 std::cout << "--rom=<filename>: Load specified ROM on startup" << std::endl;
392 std::cout << "--load=<filename>: Load specified save/movie on starup" << std::endl;
393 std::cout << "--lua=<filename>: Load specified Lua script on startup" << std::endl;
394 std::cout << "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl;
395 std::cout << "<filename>: Load specified ROM on startup" << std::endl;
396 exit_immediately = true;
397 return true;
399 if(i == "--settings")
400 settings_mode = true;
401 if(i == "--unpause")
402 start_unpaused = true;
403 if(i == "--fullscreen")
404 fullscreen_mode = true;
405 if(i == "--pluginmanager")
406 pluginmanager_mode = true;
407 if(r = regex("--set=([^=]+)=(.+)", i))
408 c_settings[r[1]] = r[2];
409 if(r = regex("--lua=(.+)", i))
410 c_lua.push_back(r[1]);
412 return true;
415 bool lsnes_app::OnInit()
417 wxApp::OnInit();
418 if(exit_immediately)
419 return false;
421 reached_main();
422 set_random_seed();
423 bring_app_foreground();
425 if(pluginmanager_mode)
426 if(!wxeditor_plugin_manager_display(NULL))
427 return false;
429 ui_services = new ui_services_type();
431 ui_thread = this_thread_id();
432 platform::init();
434 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
436 loaded_rom dummy_rom;
437 std::map<std::string, std::string> settings;
438 auto ctrldata = dummy_rom.rtype->controllerconfig(settings);
439 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
441 reinitialize_buttonmap();
442 controls.set_ports(ports);
444 std::string cfgpath = get_config_path();
445 autoload_libraries([](const std::string& libname, const std::string& error, bool system) {
446 show_message_ok(NULL, "Error loading plugin " + libname, "Error loading '" + libname + "'\n\n" +
447 error, wxICON_EXCLAMATION);
448 if(!system)
449 wxeditor_plugin_manager_notify_fail(libname);
451 messages << "Saving per-user data to: " << get_config_path() << std::endl;
452 messages << "--- Loading configuration --- " << std::endl;
453 load_configuration();
454 messages << "--- End running lsnesrc --- " << std::endl;
456 if(settings_mode) {
457 //We got to boot this up quite a bit to get the joystick driver working.
458 //In practicular, we need joystick thread and emulator thread in pause.
459 joystick_thread_handle = new thread_class(joystick_thread, 6);
460 thread_class* dummy_loop = new thread_class(eloop_helper, 8);
461 display_settings_dialog(NULL, NULL);
462 platform::exit_dummy_event_loop();
463 joystick_driver_signal();
464 joystick_thread_handle->join();
465 dummy_loop->join();
466 save_configuration();
467 return false;
469 init_lua();
471 joystick_thread_handle = new thread_class(joystick_thread, 7);
473 msg_window = new wxwin_messages();
474 msg_window->Show();
476 init_main_callbacks();
477 const std::string movie_file = get_loaded_movie(cmdline);
478 loaded_rom rom;
479 try {
480 moviefile mov;
481 rom = construct_rom(movie_file, cmdline);
482 rom.load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
483 } catch(std::exception& e) {
484 std::cerr << "Can't load ROM: " << e.what() << std::endl;
485 show_message_ok(NULL, "Error loading ROM", std::string("Error loading ROM:\n\n") +
486 e.what(), wxICON_EXCLAMATION);
487 quit_lua(); //Don't crash.
488 return false;
491 moviefile* mov = NULL;
492 if(movie_file != "")
493 try {
494 mov = new moviefile(movie_file, *rom.rtype);
495 rom.load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
496 } catch(std::exception& e) {
497 std::cerr << "Can't load state: " << e.what() << std::endl;
498 show_message_ok(NULL, "Error loading movie", std::string("Error loading movie:\n\n") +
499 e.what(), wxICON_EXCLAMATION);
500 quit_lua(); //Don't crash.
501 return false;
503 else {
504 mov = new moviefile(rom, c_settings, DEFAULT_RTC_SECOND, DEFAULT_RTC_SUBSECOND);
506 our_rom = rom;
507 mov->start_paused = start_unpaused ? !(rom.rtype && !rom.rtype->isnull()) : true;
508 for(auto i : c_lua)
509 lua_add_startup_script(i);
510 boot_emulator(rom, *mov, fullscreen_mode);
511 return true;
514 int lsnes_app::OnExit()
516 if(settings_mode)
517 return 0;
518 //NULL these so no further messages will be sent.
519 auto x = msg_window;
520 msg_window = NULL;
521 main_window = NULL;
522 if(x)
523 x->Destroy();
524 save_configuration();
525 information_dispatch::do_dump_end();
526 quit_lua();
527 movb.release_memory();
528 joystick_driver_signal();
529 joystick_thread_handle->join();
530 platform::quit();
531 cleanup_all_keys();
532 cleanup_keymapper();
533 kill_alias_binds();
534 deinitialize_wx_keyboard();
535 return 0;
538 namespace
540 struct _graphics_driver drv = {
541 .init = []() -> void {
542 initialize_wx_keyboard();
544 .quit = []() -> void {},
545 .notify_message = []() -> void
547 post_ui_event(UISERV_UPDATE_MESSAGES);
549 .notify_status = []() -> void
551 post_ui_event(UISERV_UPDATE_STATUS);
553 .notify_screen = []() -> void
555 post_ui_event(UISERV_UPDATE_SCREEN);
557 .error_message = [](const std::string& text) -> void {
558 error_message_text = text;
559 post_ui_event(UISERV_ERROR);
561 .fatal_error = []() -> void {
562 //Fun: This can be called from any thread!
563 if(ui_thread == this_thread_id()) {
564 //UI thread.
565 platform::set_modal_pause(true);
566 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"),
567 wxICON_ERROR | wxOK);
568 } else {
569 //Emulation thread panic. Signal the UI thread.
570 post_ui_event(UISERV_PANIC);
571 while(!panic_ack);
574 .name = []() -> const char* { return "wxwidgets graphics plugin"; },
575 .action_updated = []()
577 runuifun([]() -> void { main_window->action_updated(); });
579 .request_rom = [](rom_request& req)
581 rom_request* _req = &req;
582 mutex_class lock;
583 cv_class cv;
584 bool done = false;
585 umutex_class h(lock);
586 runuifun([_req, &lock, &cv, &done]() -> void {
587 try {
588 main_window->request_rom(*_req);
589 } catch(...) {
590 _req->canceled = true;
592 umutex_class h(lock);
593 done = true;
594 cv.notify_all();
596 while(!done)
597 cv.wait(h);
600 struct graphics_driver _drv(drv);
603 void signal_core_change()
605 post_ui_event(UISERV_REFRESH_TITLE);
608 void _runuifun_async(void (*fn)(void*), void* arg)
610 umutex_class h(ui_mutex);
611 ui_queue_entry e;
612 e.fn = fn;
613 e.arg = arg;
614 ui_queue.push_back(e);
615 post_ui_event(UISERV_UIFUN);
619 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
621 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir)
623 wxString _title = towxstring(title);
624 wxString _startdir = towxstring(startdir);
625 std::string filespec;
626 filespec = "All files|*";
627 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), towxstring(filespec), wxFD_OPEN);
628 if(d->ShowModal() == wxID_CANCEL)
629 throw canceled_exception();
630 std::string filename = tostdstring(d->GetPath());
631 d->Destroy();
632 if(filename == "")
633 throw canceled_exception();
634 return filename;
637 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
639 std::string filename = pick_file(parent, title, startdir);
640 //Did we pick a .zip file?
641 if(!regex_match(".*\\.[zZ][iI][pP]", filename))
642 return filename; //Not a ZIP.
643 try {
644 zip::reader zr(filename);
645 std::vector<std::string> files;
646 for(auto i : zr)
647 files.push_back(i);
648 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
649 } catch(canceled_exception& e) {
650 //Throw these forward.
651 throw;
652 } catch(...) {
653 //Ignore error.
655 return filename;
658 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
659 const std::vector<std::string>& choices, unsigned defaultchoice)
661 std::vector<wxString> _choices;
662 for(auto i : choices)
663 _choices.push_back(towxstring(i));
664 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
665 _choices.size(), &_choices[0]);
666 d2->SetSelection(defaultchoice);
667 if(d2->ShowModal() == wxID_CANCEL) {
668 d2->Destroy();
669 throw canceled_exception();
671 std::string out = tostdstring(d2->GetStringSelection());
672 d2->Destroy();
673 return out;
676 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
677 bool multiline)
679 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
680 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
681 if(d2->ShowModal() == wxID_CANCEL) {
682 d2->Destroy();
683 throw canceled_exception();
685 std::string text = tostdstring(d2->GetValue());
686 d2->Destroy();
687 return text;
690 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
692 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
693 d3->ShowModal();
694 d3->Destroy();