Actually call on_reset callback
[lsnes.git] / src / platform / wxwidgets / main.cpp
blob40562156230e4546e197c4b1a6b9882b93ddd5f7
1 #include <wx/wx.h>
3 #include "lsnes.hpp"
5 #include "core/audioapi.hpp"
6 #include "core/command.hpp"
7 #include "core/controller.hpp"
8 #include "core/dispatch.hpp"
9 #include "core/framerate.hpp"
10 #include "core/joystickapi.hpp"
11 #include "core/keymapper.hpp"
12 #include "core/loadlib.hpp"
13 #include "lua/lua.hpp"
14 #include "core/advdumper.hpp"
15 #include "core/mainloop.hpp"
16 #include "core/messages.hpp"
17 #include "core/misc.hpp"
18 #include "core/instance.hpp"
19 #include "core/misc.hpp"
20 #include "core/moviefile-common.hpp"
21 #include "core/moviedata.hpp"
22 #include "core/random.hpp"
23 #include "core/rom.hpp"
24 #include "core/settings.hpp"
25 #include "core/window.hpp"
26 #include "interface/romtype.hpp"
27 #include "library/crandom.hpp"
28 #include "library/directory.hpp"
29 #include "library/running-executable.hpp"
30 #include "library/string.hpp"
31 #include "library/threads.hpp"
32 #include "library/utf8.hpp"
33 #include "library/zip.hpp"
35 #include "platform/wxwidgets/settings-common.hpp"
36 #include "platform/wxwidgets/platform.hpp"
37 #include "platform/wxwidgets/window_messages.hpp"
38 #include "platform/wxwidgets/window_status.hpp"
39 #include "platform/wxwidgets/window_mainwindow.hpp"
41 #include <functional>
42 #include <math.h>
43 #include <cassert>
45 #include <wx/wx.h>
46 #include <wx/event.h>
47 #include <wx/control.h>
48 #include <wx/combobox.h>
49 #include <wx/cmdline.h>
50 #include <iostream>
52 #ifdef __WXMAC__
53 #error "Mac OS is not supported"
54 #endif
56 #define UISERV_REFRESH_TITLE 9990
57 #define UISERV_RESIZED 9991
58 #define UISERV_UIFUN 9992
59 #define UISERV_EXIT 9994
60 #define UISERV_PANIC 9998
61 #define UISERV_ERROR 9999
63 wxwin_messages* msg_window;
64 wxwin_mainwindow* main_window;
65 std::string our_rom_name;
67 bool wxwidgets_exiting = false;
69 namespace
71 threads::id ui_thread;
72 volatile bool panic_ack = false;
73 std::string error_message_text;
74 volatile bool modal_dialog_confirm;
75 volatile bool modal_dialog_active;
76 threads::lock ui_mutex;
77 threads::cv ui_condition;
78 bool preboot_env = true;
79 runuifun_once_ctx screenupdate_once;
80 runuifun_once_ctx statusupdate_once;
81 runuifun_once_ctx message_once;
83 struct uiserv_event : public wxEvent
85 uiserv_event(int code)
87 SetId(code);
90 wxEvent* Clone() const
92 return new uiserv_event(*this);
96 class ui_services_type : public wxEvtHandler
98 bool ProcessEvent(wxEvent& event);
101 struct ui_queue_entry
103 void(*fn)(void*);
104 void* arg;
105 runuifun_once_ctx* ctx;
108 std::list<ui_queue_entry> ui_queue;
110 void do_panic()
112 std::string msg = "Panic: Unrecoverable error, can't continue";
113 std::string msg2 = platform::msgbuf.get_last_message();
114 if(msg2 != "")
115 msg += "\n\n" + msg2;
116 wxMessageBox(towxstring(msg), _T("Error"), wxICON_ERROR | wxOK);
119 bool ui_services_type::ProcessEvent(wxEvent& event)
121 CHECK_UI_THREAD;
122 int c = event.GetId();
123 if(c == UISERV_PANIC) {
124 //We need to panic.
125 do_panic();
126 panic_ack = true;
127 } else if(c == UISERV_REFRESH_TITLE) {
128 if(main_window)
129 main_window->refresh_title();
130 } else if(c == UISERV_RESIZED) {
131 if(main_window)
132 main_window->notify_resized();
133 } else if(c == UISERV_ERROR) {
134 std::string text = error_message_text;
135 wxMessageBox(towxstring(text), _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, main_window);
136 } else if(c == UISERV_EXIT) {
137 if(main_window)
138 main_window->notify_exit();
139 } else if(c == UISERV_UIFUN) {
140 std::list<ui_queue_entry>::iterator i;
141 ui_queue_entry e;
142 queue_synchronous_fn_warning = true;
143 back:
145 threads::alock h(ui_mutex);
146 if(ui_queue.empty())
147 goto end;
148 i = ui_queue.begin();
149 e = *i;
150 ui_queue.erase(i);
152 if(e.ctx) e.ctx->clear_flag();
153 e.fn(e.arg);
154 goto back;
155 end:
156 queue_synchronous_fn_warning = false;
158 return true;
161 ui_services_type* ui_services;
163 void post_ui_event(int code)
165 uiserv_event uic(code);
166 wxPostEvent(ui_services, uic);
169 std::string loaded_pdev;
170 std::string loaded_rdev;
172 double from_logscale(double v)
174 return exp(v);
177 void handle_config_line(std::string line)
179 regex_results r;
180 if(r = regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
181 lsnes_instance.setcache->set(r[1], r[2], true);
182 messages << "Setting " << r[1] << " set to " << r[2] << std::endl;
183 } else if(r = regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
184 if(!lsnes_instance.command->valid_alias_name(r[1])) {
185 messages << "Illegal alias name " << r[1] << std::endl;
186 return;
188 std::string tmp = lsnes_instance.command->get_alias_for(r[1]);
189 tmp = tmp + r[2] + "\n";
190 lsnes_instance.command->set_alias_for(r[1], tmp);
191 messages << r[1] << " aliased to " << r[2] << std::endl;
192 } else if(r = regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line)) {
193 std::string tmp = r[4];
194 regex_results r2 = regex("(load|load-smart|load-readonly|load-preserve|load-state"
195 "|load-movie|save-state|save-movie)[ \t]+\\$\\{project\\}(.*)\\.lsmv", tmp);
196 if(r2) tmp = r2[1] + " $SLOT:" + r2[2];
197 lsnes_instance.mapper->bind(r[1], r[2], r[3], tmp);
198 if(r[1] != "" || r[2] != "")
199 messages << r[1] << "/" << r[2] << " ";
200 messages << r[3] << " bound to '" << tmp << "'" << std::endl;
201 } else if(r = regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line)) {
202 keyboard::ctrlrkey* ckey = lsnes_instance.mapper->get_controllerkey(r[2]);
203 if(ckey) {
204 ckey->append(r[1]);
205 messages << r[1] << " bound (button) to " << r[2] << std::endl;
206 } else
207 lsnes_instance.buttons->button_keys[r[2]] = r[1];
208 } else if(r = regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
209 if(r[2] != "") {
210 core_selections[r[1]] = r[2];
211 messages << "Prefer " << r[2] << " for " << r[1] << std::endl;
213 } else if(r = regex("AUDIO_PDEV[ \t]+([^ \t].*)", line)) {
214 loaded_pdev = r[1];
215 } else if(r = regex("AUDIO_RDEV[ \t]+([^ \t].*)", line)) {
216 loaded_rdev = r[1];
217 } else if(r = regex("AUDIO_GVOL[ \t]+([^ \t].*)", line)) {
218 lsnes_instance.audio->music_volume(from_logscale(parse_value<double>(r[1])));
219 } else if(r = regex("AUDIO_RVOL[ \t]+([^ \t].*)", line)) {
220 lsnes_instance.audio->voicer_volume(from_logscale(parse_value<double>(r[1])));
221 } else if(r = regex("AUDIO_PVOL[ \t]+([^ \t].*)", line)) {
222 lsnes_instance.audio->voicep_volume(from_logscale(parse_value<double>(r[1])));
223 } else if(r = regex("VIDEO_ARC[ \t]*", line)) {
224 arcorrect_enabled = true;
225 } else if(r = regex("VIDEO_HFLIP[ \t]*", line)) {
226 hflip_enabled = true;
227 } else if(r = regex("VIDEO_VFLIP[ \t]*", line)) {
228 vflip_enabled = true;
229 } else if(r = regex("VIDEO_ROTATE[ \t]*", line)) {
230 rotate_enabled = true;
231 } else if(r = regex("VIDEO_SFACT[ \t]+([^ \t].*)", line)) {
232 double val = parse_value<double>(r[1]);
233 if(val < 0.1 || val > 10) throw std::runtime_error("Crazy scale factor");
234 video_scale_factor = val;
235 } else if(r = regex("VIDEO_SFLAGS[ \t]+([^ \t].*)", line)) {
236 scaling_flags = parse_value<uint32_t>(r[1]);
237 } else
238 (stringfmt() << "Unrecognized directive: " << line).throwex();
241 void load_configuration()
243 std::string cfg = get_config_path() + "/lsneswxw.cfg";
244 std::ifstream cfgfile(cfg.c_str());
245 std::string line;
246 size_t lineno = 1;
247 while(std::getline(cfgfile, line)) {
248 try {
249 handle_config_line(line);
250 } catch(std::exception& e) {
251 messages << "Error processing line " << lineno << ": " << e.what() << std::endl;
253 lineno++;
255 platform::set_sound_device_by_description(loaded_pdev, loaded_rdev);
256 (*lsnes_instance.abindmanager)();
257 lsnes_uri_rewrite.load(get_config_path() + "/lsnesurirewrite.cfg");
260 double to_logscale(double v)
262 if(fabs(v) < 1e-15)
263 return -999.0;
264 return log(fabs(v));
267 void save_configuration()
269 std::string cfg = get_config_path() + "/lsneswxw.cfg";
270 std::string cfgtmp = cfg + ".tmp";
271 std::ofstream cfgfile(cfgtmp.c_str());
272 //Settings.
273 for(auto i : lsnes_instance.setcache->get_all())
274 cfgfile << "SET " << i.first << " " << i.second << std::endl;
275 //Aliases.
276 for(auto i : lsnes_instance.command->get_aliases()) {
277 std::string old_alias_value = lsnes_instance.command->get_alias_for(i);
278 while(old_alias_value != "") {
279 std::string aliasline;
280 size_t s = old_alias_value.find_first_of("\n");
281 if(s < old_alias_value.length()) {
282 aliasline = old_alias_value.substr(0, s);
283 old_alias_value = old_alias_value.substr(s + 1);
284 } else {
285 aliasline = old_alias_value;
286 old_alias_value = "";
288 cfgfile << "ALIAS " << i << " " << aliasline << std::endl;
291 //Keybindings.
292 for(auto i : lsnes_instance.mapper->get_bindings())
293 cfgfile << "BIND " << std::string(i) << " " << lsnes_instance.mapper->get(i) << std::endl;
294 //Buttons.
295 for(auto i : lsnes_instance.mapper->get_controller_keys()) {
296 std::string b;
297 unsigned idx = 0;
298 while((b = i->get_string(idx++)) != "")
299 cfgfile << "BUTTON " << b << " " << i->get_command() << std::endl;
301 for(auto i : lsnes_instance.buttons->button_keys)
302 cfgfile << "BUTTON " << i.second << " " << i.first << std::endl;
303 for(auto i : core_selections)
304 if(i.second != "")
305 cfgfile << "PREFER " << i.first << " " << i.second << std::endl;
306 //Sound config.
307 cfgfile << "AUDIO_PDEV " << platform::get_sound_device_description(false) << std::endl;
308 cfgfile << "AUDIO_RDEV " << platform::get_sound_device_description(true) << std::endl;
309 cfgfile << "AUDIO_GVOL " << to_logscale(lsnes_instance.audio->music_volume()) << std::endl;
310 cfgfile << "AUDIO_RVOL " << to_logscale(lsnes_instance.audio->voicer_volume()) << std::endl;
311 cfgfile << "AUDIO_PVOL " << to_logscale(lsnes_instance.audio->voicep_volume()) << std::endl;
312 cfgfile << "VIDEO_SFACT " << video_scale_factor << std::endl;
313 cfgfile << "VIDEO_SFLAGS " << scaling_flags << std::endl;
314 if(arcorrect_enabled) cfgfile << "VIDEO_ARC" << std::endl;
315 if(hflip_enabled) cfgfile << "VIDEO_HFLIP" << std::endl;
316 if(vflip_enabled) cfgfile << "VIDEO_VFLIP" << std::endl;
317 if(rotate_enabled) cfgfile << "VIDEO_ROTATE" << std::endl;
318 if(!cfgfile) {
319 show_message_ok(NULL, "Error Saving configuration", "Error saving configuration",
320 wxICON_EXCLAMATION);
321 return;
323 cfgfile.close();
324 directory::rename_overwrite(cfgtmp.c_str(), cfg.c_str());
325 //Last save.
326 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
327 lsave << last_save;
328 lsnes_uri_rewrite.save(get_config_path() + "/lsnesurirewrite.cfg");
331 void* eloop_helper(int x)
333 platform::dummy_event_loop();
334 return NULL;
337 std::string get_loaded_movie(const std::vector<std::string>& cmdline)
339 for(auto i : cmdline)
340 if(!i.empty() && i[0] != '-')
341 return i;
342 return "";
346 wxString towxstring(const std::string& str)
348 return wxString(str.c_str(), wxConvUTF8);
351 std::string tostdstring(const wxString& str)
353 return std::string(str.mb_str(wxConvUTF8));
356 wxString towxstring(const std::u32string& str)
358 return wxString(utf8::to8(str).c_str(), wxConvUTF8);
361 std::u32string tou32string(const wxString& str)
363 return utf8::to32(std::string(str.mb_str(wxConvUTF8)));
366 std::string pick_archive_member(wxWindow* parent, const std::string& filename)
368 CHECK_UI_THREAD;
369 //Did we pick a .zip file?
370 std::string f;
371 try {
372 zip::reader zr(filename);
373 std::vector<wxString> files;
374 for(auto i : zr)
375 files.push_back(towxstring(i));
376 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
377 wxT("Select member"), files.size(), &files[0]);
378 if(d2->ShowModal() == wxID_CANCEL) {
379 d2->Destroy();
380 return "";
382 f = filename + "/" + tostdstring(d2->GetStringSelection());
383 d2->Destroy();
384 } catch(...) {
385 //Ignore error.
386 f = filename;
388 return f;
391 void signal_program_exit()
393 post_ui_event(UISERV_EXIT);
396 void signal_resize_needed()
398 post_ui_event(UISERV_RESIZED);
402 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
403 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
404 wxCMD_LINE_PARAM_MULTIPLE },
405 { wxCMD_LINE_NONE }
408 class lsnes_app : public wxApp
410 public:
411 lsnes_app();
412 virtual bool OnInit();
413 virtual int OnExit();
414 virtual void OnInitCmdLine(wxCmdLineParser& parser);
415 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
416 private:
417 bool settings_mode;
418 bool pluginmanager_mode;
419 std::string c_rom;
420 std::string c_file;
421 std::vector<std::string> cmdline;
422 std::map<std::string, std::string> c_settings;
423 std::vector<std::string> c_lua;
424 std::vector<std::string> c_library;
425 bool exit_immediately;
426 bool fullscreen_mode;
427 bool start_unpaused;
428 struct dispatch::target<> screenupdate;
429 struct dispatch::target<> statusupdate;
430 struct dispatch::target<> actionupdate;
433 IMPLEMENT_APP(lsnes_app)
435 lsnes_app::lsnes_app()
437 settings_mode = false;
438 pluginmanager_mode = false;
439 exit_immediately = false;
440 fullscreen_mode = false;
441 start_unpaused = false;
444 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
446 parser.SetDesc(dummy_descriptor_table);
447 parser.SetSwitchChars(wxT(""));
450 static bool regex_sanity_check()
452 bool regex_sane = true;
453 try {
454 //Simple sanity checks.
455 regex_sane &= regex_match("foo.*baz", "foobarbaz", REGEX_MATCH_REGEX);
456 regex_sane &= regex_match(".*foo.*baz.*", "foobarbaz", REGEX_MATCH_REGEX);
457 regex_sane &= regex_match("foo*baz", "FOOBARBAZ", REGEX_MATCH_IWILDCARDS);
458 regex_sane &= regex_match("foo.*baz", "FOOBARBAZ", REGEX_MATCH_IREGEX);
459 } catch(...) {
460 regex_sane = false;
462 return regex_sane;
465 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
467 for(size_t i = 0; i< parser.GetParamCount(); i++)
468 cmdline.push_back(tostdstring(parser.GetParam(i)));
469 for(auto i: cmdline) {
470 regex_results r;
471 if(i == "--help" || i == "-h") {
472 std::cout << "--settings: Show the settings dialog" << std::endl;
473 std::cout << "--pluginmanager: Show the plugin manager" << std::endl;
474 std::cout << "--fullscreen: Start fullscreen" << std::endl;
475 std::cout << "--unpause: Start unpaused (only if ROM is loaded)" << std::endl;
476 std::cout << "--rom=<filename>: Load specified ROM on startup" << std::endl;
477 std::cout << "--load=<filename>: Load specified save/movie on starup" << std::endl;
478 std::cout << "--lua=<filename>: Load specified Lua script on startup" << std::endl;
479 std::cout << "--library=<filename>: Load specified library on startup" << std::endl;
480 std::cout << "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl;
481 std::cout << "--sanity-check: Perfrom some simple sanity checks" << std::endl;
482 std::cout << "<filename>: Load specified ROM on startup" << std::endl;
483 exit_immediately = true;
484 return true;
486 if(i == "--settings")
487 settings_mode = true;
488 if(i == "--unpause")
489 start_unpaused = true;
490 if(i == "--fullscreen")
491 fullscreen_mode = true;
492 if(i == "--pluginmanager")
493 pluginmanager_mode = true;
494 if(r = regex("--set=([^=]+)=(.+)", i))
495 c_settings[r[1]] = r[2];
496 if(r = regex("--lua=(.+)", i))
497 c_lua.push_back(r[1]);
498 if(r = regex("--library=(.+)", i))
499 c_library.push_back(r[1]);
500 if(i == "--sanity-check") {
501 if(regex_sanity_check()) {
502 std::cout << "Regex library passes basic sanity checks." << std::endl;
503 } else {
504 std::cout << "Regex library FAILS basic sanity checks." << std::endl;
506 std::cout << "Executable: '" << running_executable() << "'" << std::endl;
507 std::cout << "Configuration directory: '" << get_config_path()
508 << "'" << std::endl;
509 std::cout << "System autoload directory: '" << loadlib_debug_get_system_library_dir()
510 << "'" << std::endl;
511 std::cout << "User autoload directory: '" << loadlib_debug_get_user_library_dir()
512 << "'" << std::endl;
513 exit_immediately = true;
516 return true;
519 bool lsnes_app::OnInit()
521 wxApp::OnInit();
522 if(exit_immediately)
523 return false;
525 screenupdate.set(lsnes_instance.dispatch->screen_update, []() {
526 runuifun(screenupdate_once, []() {
527 if(main_window)
528 main_window->notify_update();
529 wxwindow_memorysearch_update(CORE());
530 wxwindow_tasinput_update(CORE());
533 statusupdate.set(lsnes_instance.dispatch->status_update, []() {
534 runuifun(statusupdate_once, []() {
535 if(main_window)
536 main_window->notify_update_status();
537 wxeditor_movie_update(CORE());
538 wxeditor_hexeditor_update(CORE());
541 actionupdate.set(lsnes_instance.dispatch->action_update, []() {
542 //This can be called early, so check for main_window existing.
543 if(main_window)
544 main_window->action_updated();
547 try {
548 crandom::init();
549 } catch(std::exception& e) {
550 show_message_ok(NULL, "RNG error", "Error initializing system RNG", wxICON_ERROR);
551 return false;
554 if(!regex_sanity_check()) {
555 wxMessageBox(towxstring("Regex sanity check FAILED.\n\nExpect problems."),
556 _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, NULL);
559 reached_main();
560 set_random_seed();
562 if(pluginmanager_mode)
563 if(!wxeditor_plugin_manager_display(NULL))
564 return false;
566 ui_services = new ui_services_type();
568 ui_thread = threads::this_id();
569 platform::init();
571 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
573 loaded_rom dummy_rom;
574 std::map<std::string, std::string> settings;
575 auto ctrldata = dummy_rom.controllerconfig(settings);
576 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
578 lsnes_instance.buttons->reinit();
579 lsnes_instance.controls->set_ports(ports);
581 std::string cfgpath = get_config_path();
582 autoload_libraries([](const std::string& libname, const std::string& error, bool system) {
583 show_message_ok(NULL, "Error loading plugin " + libname, "Error loading '" + libname + "'\n\n" +
584 error, wxICON_EXCLAMATION);
585 if(!system)
586 wxeditor_plugin_manager_notify_fail(libname);
588 messages << "Saving per-user data to: " << get_config_path() << std::endl;
589 messages << "--- Loading configuration --- " << std::endl;
590 load_configuration();
591 messages << "--- End running lsnesrc --- " << std::endl;
593 if(settings_mode) {
594 //We got to boot this up quite a bit to get the joystick driver working.
595 //In practicular, we need joystick thread and emulator thread in pause.
596 threads::thread* dummy_loop = new threads::thread(eloop_helper, 8);
597 display_settings_dialog(NULL, lsnes_instance, NULL);
598 platform::exit_dummy_event_loop();
599 joystick_driver_quit();
600 dummy_loop->join();
601 save_configuration();
602 return false;
604 init_lua(lsnes_instance);
605 lsnes_instance.mdumper->set_output(&messages.getstream());
607 msg_window = new wxwin_messages(lsnes_instance);
608 msg_window->Show();
610 init_main_callbacks();
612 //Load libraries before trying to load movie, in case there are cores in there.
613 for(auto i : c_library) {
614 try {
615 with_loaded_library(*new loadlib::module(loadlib::library(i)));
616 } catch(std::exception& e) {
617 show_message_ok(NULL, "Error loading library", std::string("Error loading library '") +
618 i + "':\n\n" + e.what(), wxICON_EXCLAMATION);
622 const std::string movie_file = get_loaded_movie(cmdline);
623 loaded_rom rom;
624 try {
625 moviefile mov;
626 rom = construct_rom(movie_file, cmdline);
627 rom.load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
628 } catch(std::exception& e) {
629 std::cerr << "Can't load ROM: " << e.what() << std::endl;
630 show_message_ok(NULL, "Error loading ROM", std::string("Error loading ROM:\n\n") +
631 e.what(), wxICON_EXCLAMATION);
632 quit_lua(lsnes_instance); //Don't crash.
633 return false;
636 moviefile* mov = NULL;
637 if(movie_file != "")
638 try {
639 mov = new moviefile(movie_file, rom.get_internal_rom_type());
640 rom.load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
641 } catch(std::exception& e) {
642 std::cerr << "Can't load state: " << e.what() << std::endl;
643 show_message_ok(NULL, "Error loading movie", std::string("Error loading movie:\n\n") +
644 e.what(), wxICON_EXCLAMATION);
645 quit_lua(lsnes_instance); //Don't crash.
646 return false;
648 else {
649 mov = new moviefile(rom, c_settings, DEFAULT_RTC_SECOND, DEFAULT_RTC_SUBSECOND);
651 *lsnes_instance.rom = rom;
652 mov->start_paused = start_unpaused ? rom.isnull() : true;
653 for(auto i : c_lua)
654 lsnes_instance.lua2->add_startup_script(i);
655 preboot_env = false;
656 boot_emulator(lsnes_instance, rom, *mov, fullscreen_mode);
657 return true;
660 int lsnes_app::OnExit()
662 if(settings_mode)
663 return 0;
664 //NULL these so no further messages will be sent.
665 auto x = msg_window;
666 msg_window = NULL;
667 main_window = NULL;
668 if(x)
669 x->Destroy();
670 save_configuration();
671 quit_lua(lsnes_instance);
672 lsnes_instance.mlogic->release_memory();
673 platform::quit();
674 lsnes_instance.buttons->cleanup();
675 cleanup_keymapper();
676 deinitialize_wx_mouse(lsnes_instance);
677 deinitialize_wx_keyboard(lsnes_instance);
678 return 0;
681 void do_save_configuration()
683 save_configuration();
686 namespace
688 struct _graphics_driver drv = {
689 .init = []() -> void {
690 initialize_wx_keyboard(lsnes_instance);
691 initialize_wx_mouse(lsnes_instance);
693 .quit = []() -> void {},
694 .notify_message = []() -> void
696 runuifun(message_once, []() {
697 if(msg_window)
698 msg_window->notify_update();
701 .error_message = [](const std::string& text) -> void {
702 error_message_text = text;
703 post_ui_event(UISERV_ERROR);
705 .fatal_error = []() -> void {
706 //Fun: This can be called from any thread!
707 if(ui_thread == threads::this_id()) {
708 //UI thread.
709 platform::set_modal_pause(true);
710 do_panic();
711 } else {
712 //Emulation thread panic. Signal the UI thread.
713 post_ui_event(UISERV_PANIC);
714 while(!panic_ack);
717 .name = []() -> const char* { return "wxwidgets graphics plugin"; },
718 .request_rom = [](rom_request& req)
720 rom_request* _req = &req;
721 threads::lock lock;
722 threads::cv cv;
723 bool done = false;
724 if(preboot_env) {
725 try {
726 //main_window is NULL, hope this does not crash.
727 main_window->request_rom(*_req);
728 } catch(...) {
729 _req->canceled = true;
731 return;
733 threads::alock h(lock);
734 runuifun([_req, &lock, &cv, &done]() -> void {
735 try {
736 main_window->request_rom(*_req);
737 } catch(...) {
738 _req->canceled = true;
740 threads::alock h(lock);
741 done = true;
742 cv.notify_all();
744 while(!done)
745 cv.wait(h);
748 struct graphics_driver _drv(drv);
751 void signal_core_change()
753 post_ui_event(UISERV_REFRESH_TITLE);
756 void _runuifun_async(runuifun_once_ctx* ctx, void (*fn)(void*), void* arg)
758 if(ctx && !ctx->set_flag()) return;
759 threads::alock h(ui_mutex);
760 ui_queue_entry e;
761 e.fn = fn;
762 e.arg = arg;
763 e.ctx = ctx;
764 ui_queue.push_back(e);
765 post_ui_event(UISERV_UIFUN);
769 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
771 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir)
773 CHECK_UI_THREAD;
774 wxString _title = towxstring(title);
775 wxString _startdir = towxstring(startdir);
776 std::string filespec;
777 filespec = "All files|*";
778 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), towxstring(filespec), wxFD_OPEN);
779 if(d->ShowModal() == wxID_CANCEL)
780 throw canceled_exception();
781 std::string filename = tostdstring(d->GetPath());
782 d->Destroy();
783 if(filename == "")
784 throw canceled_exception();
785 return filename;
788 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
790 CHECK_UI_THREAD;
791 std::string filename = pick_file(parent, title, startdir);
792 //Did we pick a .zip file?
793 if(!regex_match(".*\\.[zZ][iI][pP]", filename))
794 return filename; //Not a ZIP.
795 try {
796 zip::reader zr(filename);
797 std::vector<std::string> files;
798 for(auto i : zr)
799 files.push_back(i);
800 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
801 } catch(canceled_exception& e) {
802 //Throw these forward.
803 throw;
804 } catch(...) {
805 //Ignore error.
807 return filename;
810 unsigned pick_among_index(wxWindow* parent, const std::string& title, const std::string& prompt,
811 const std::vector<std::string>& choices, unsigned defaultchoice)
813 CHECK_UI_THREAD;
814 std::vector<wxString> _choices;
815 for(auto i : choices)
816 _choices.push_back(towxstring(i));
817 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
818 _choices.size(), &_choices[0]);
819 d2->SetSelection(defaultchoice);
820 if(d2->ShowModal() == wxID_CANCEL) {
821 d2->Destroy();
822 throw canceled_exception();
824 unsigned idx = d2->GetSelection();
825 d2->Destroy();
826 return idx;
829 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
830 const std::vector<std::string>& choices, unsigned defaultchoice)
832 unsigned idx = pick_among_index(parent, title, prompt, choices, defaultchoice);
833 if(idx < choices.size())
834 return choices[idx];
835 throw canceled_exception();
838 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
839 bool multiline)
841 CHECK_UI_THREAD;
842 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
843 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
844 if(d2->ShowModal() == wxID_CANCEL) {
845 d2->Destroy();
846 throw canceled_exception();
848 std::string text = tostdstring(d2->GetValue());
849 d2->Destroy();
850 return text;
853 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
855 CHECK_UI_THREAD;
856 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
857 d3->ShowModal();
858 d3->Destroy();
861 bool run_show_error(wxWindow* parent, const std::string& title, const std::string& text, std::function<void()> fn)
863 try {
864 fn();
865 return false;
866 } catch(std::exception& e) {
867 std::string err = e.what();
868 std::string _title = title;
869 std::string _text = (text == "") ? err : (text + ": " + err);
870 runuifun([parent, _title, _text]() {
871 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
873 return true;
877 void show_exception(wxWindow* parent, const std::string& title, const std::string& text, std::exception& e)
879 CHECK_UI_THREAD;
880 std::string err = e.what();
881 std::string _title = title;
882 std::string _text = (text == "") ? err : (text + ": " + err);
883 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
886 void show_exception_any(wxWindow* parent, const std::string& title, const std::string& text, std::exception& e)
888 std::string err = e.what();
889 std::string _title = title;
890 std::string _text = (text == "") ? err : (text + ": " + err);
891 runuifun([parent, _title, _text]() {
892 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
896 void _check_ui_thread(const char* file, int line)
898 if(ui_thread == threads::this_id())
899 return;
900 std::cerr << "UI routine running in wrong thread at " << file << ":" << line << std::endl;