Make wrapper for boost::lexical_cast
[lsnes.git] / src / platform / wxwidgets / main.cpp
blob76bb1e54840d1356a4b4465acb9e70e9a349593c
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 <math.h>
42 #include <cassert>
44 #include <wx/wx.h>
45 #include <wx/event.h>
46 #include <wx/control.h>
47 #include <wx/combobox.h>
48 #include <wx/cmdline.h>
49 #include <iostream>
51 #define UISERV_REFRESH_TITLE 9990
52 #define UISERV_RESIZED 9991
53 #define UISERV_UIFUN 9992
54 #define UISERV_EXIT 9994
55 #define UISERV_PANIC 9998
56 #define UISERV_ERROR 9999
58 wxwin_messages* msg_window;
59 wxwin_mainwindow* main_window;
60 std::string our_rom_name;
62 bool wxwidgets_exiting = false;
64 namespace
66 threads::id ui_thread;
67 volatile bool panic_ack = false;
68 std::string error_message_text;
69 volatile bool modal_dialog_confirm;
70 volatile bool modal_dialog_active;
71 threads::lock ui_mutex;
72 threads::cv ui_condition;
73 bool preboot_env = true;
74 runuifun_once_ctx screenupdate_once;
75 runuifun_once_ctx statusupdate_once;
76 runuifun_once_ctx message_once;
78 struct uiserv_event : public wxEvent
80 uiserv_event(int code)
82 SetId(code);
85 wxEvent* Clone() const
87 return new uiserv_event(*this);
91 class ui_services_type : public wxEvtHandler
93 bool ProcessEvent(wxEvent& event);
96 struct ui_queue_entry
98 void(*fn)(void*);
99 void* arg;
100 runuifun_once_ctx* ctx;
103 std::list<ui_queue_entry> ui_queue;
105 void do_panic()
107 std::string msg = "Panic: Unrecoverable error, can't continue";
108 std::string msg2 = platform::msgbuf.get_last_message();
109 if(msg2 != "")
110 msg += "\n\n" + msg2;
111 wxMessageBox(towxstring(msg), _T("Error"), wxICON_ERROR | wxOK);
114 bool ui_services_type::ProcessEvent(wxEvent& event)
116 CHECK_UI_THREAD;
117 int c = event.GetId();
118 if(c == UISERV_PANIC) {
119 //We need to panic.
120 do_panic();
121 panic_ack = true;
122 } else if(c == UISERV_REFRESH_TITLE) {
123 if(main_window)
124 main_window->refresh_title();
125 } else if(c == UISERV_RESIZED) {
126 if(main_window)
127 main_window->notify_resized();
128 } else if(c == UISERV_ERROR) {
129 std::string text = error_message_text;
130 wxMessageBox(towxstring(text), _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, main_window);
131 } else if(c == UISERV_EXIT) {
132 if(main_window)
133 main_window->notify_exit();
134 } else if(c == UISERV_UIFUN) {
135 std::list<ui_queue_entry>::iterator i;
136 ui_queue_entry e;
137 queue_synchronous_fn_warning = true;
138 back:
140 threads::alock h(ui_mutex);
141 if(ui_queue.empty())
142 goto end;
143 i = ui_queue.begin();
144 e = *i;
145 ui_queue.erase(i);
147 if(e.ctx) e.ctx->clear_flag();
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 std::string loaded_pdev;
165 std::string loaded_rdev;
167 double from_logscale(double v)
169 return exp(v);
172 void handle_config_line(std::string line)
174 regex_results r;
175 if(r = regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
176 lsnes_instance.setcache->set(r[1], r[2], true);
177 messages << "Setting " << r[1] << " set to " << r[2] << std::endl;
178 } else if(r = regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
179 if(!lsnes_instance.command->valid_alias_name(r[1])) {
180 messages << "Illegal alias name " << r[1] << std::endl;
181 return;
183 std::string tmp = lsnes_instance.command->get_alias_for(r[1]);
184 tmp = tmp + r[2] + "\n";
185 lsnes_instance.command->set_alias_for(r[1], tmp);
186 messages << r[1] << " aliased to " << r[2] << std::endl;
187 } else if(r = regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line)) {
188 std::string tmp = r[4];
189 regex_results r2 = regex("(load|load-smart|load-readonly|load-preserve|load-state"
190 "|load-movie|save-state|save-movie)[ \t]+\\$\\{project\\}(.*)\\.lsmv", tmp);
191 if(r2) tmp = r2[1] + " $SLOT:" + r2[2];
192 lsnes_instance.mapper->bind(r[1], r[2], r[3], tmp);
193 if(r[1] != "" || r[2] != "")
194 messages << r[1] << "/" << r[2] << " ";
195 messages << r[3] << " bound to '" << tmp << "'" << std::endl;
196 } else if(r = regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line)) {
197 keyboard::ctrlrkey* ckey = lsnes_instance.mapper->get_controllerkey(r[2]);
198 if(ckey) {
199 ckey->append(r[1]);
200 messages << r[1] << " bound (button) to " << r[2] << std::endl;
201 } else
202 lsnes_instance.buttons->button_keys[r[2]] = r[1];
203 } else if(r = regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
204 if(r[2] != "") {
205 core_selections[r[1]] = r[2];
206 messages << "Prefer " << r[2] << " for " << r[1] << std::endl;
208 } else if(r = regex("AUDIO_PDEV[ \t]+([^ \t].*)", line)) {
209 loaded_pdev = r[1];
210 } else if(r = regex("AUDIO_RDEV[ \t]+([^ \t].*)", line)) {
211 loaded_rdev = r[1];
212 } else if(r = regex("AUDIO_GVOL[ \t]+([^ \t].*)", line)) {
213 lsnes_instance.audio->music_volume(from_logscale(parse_value<double>(r[1])));
214 } else if(r = regex("AUDIO_RVOL[ \t]+([^ \t].*)", line)) {
215 lsnes_instance.audio->voicer_volume(from_logscale(parse_value<double>(r[1])));
216 } else if(r = regex("AUDIO_PVOL[ \t]+([^ \t].*)", line)) {
217 lsnes_instance.audio->voicep_volume(from_logscale(parse_value<double>(r[1])));
218 } else if(r = regex("VIDEO_ARC[ \t]*", line)) {
219 arcorrect_enabled = true;
220 } else if(r = regex("VIDEO_HFLIP[ \t]*", line)) {
221 hflip_enabled = true;
222 } else if(r = regex("VIDEO_VFLIP[ \t]*", line)) {
223 vflip_enabled = true;
224 } else if(r = regex("VIDEO_ROTATE[ \t]*", line)) {
225 rotate_enabled = true;
226 } else if(r = regex("VIDEO_SFACT[ \t]+([^ \t].*)", line)) {
227 double val = parse_value<double>(r[1]);
228 if(val < 0.1 || val > 10) throw std::runtime_error("Crazy scale factor");
229 video_scale_factor = val;
230 } else if(r = regex("VIDEO_SFLAGS[ \t]+([^ \t].*)", line)) {
231 scaling_flags = parse_value<uint32_t>(r[1]);
232 } else
233 (stringfmt() << "Unrecognized directive: " << line).throwex();
236 void load_configuration()
238 std::string cfg = get_config_path() + "/lsneswxw.cfg";
239 std::ifstream cfgfile(cfg.c_str());
240 std::string line;
241 size_t lineno = 1;
242 while(std::getline(cfgfile, line)) {
243 try {
244 handle_config_line(line);
245 } catch(std::exception& e) {
246 messages << "Error processing line " << lineno << ": " << e.what() << std::endl;
248 lineno++;
250 platform::set_sound_device_by_description(loaded_pdev, loaded_rdev);
251 (*lsnes_instance.abindmanager)();
252 lsnes_uri_rewrite.load(get_config_path() + "/lsnesurirewrite.cfg");
255 double to_logscale(double v)
257 if(fabs(v) < 1e-15)
258 return -999.0;
259 return log(fabs(v));
262 void save_configuration()
264 std::string cfg = get_config_path() + "/lsneswxw.cfg";
265 std::string cfgtmp = cfg + ".tmp";
266 std::ofstream cfgfile(cfgtmp.c_str());
267 //Settings.
268 for(auto i : lsnes_instance.setcache->get_all())
269 cfgfile << "SET " << i.first << " " << i.second << std::endl;
270 //Aliases.
271 for(auto i : lsnes_instance.command->get_aliases()) {
272 std::string old_alias_value = lsnes_instance.command->get_alias_for(i);
273 while(old_alias_value != "") {
274 std::string aliasline;
275 size_t s = old_alias_value.find_first_of("\n");
276 if(s < old_alias_value.length()) {
277 aliasline = old_alias_value.substr(0, s);
278 old_alias_value = old_alias_value.substr(s + 1);
279 } else {
280 aliasline = old_alias_value;
281 old_alias_value = "";
283 cfgfile << "ALIAS " << i << " " << aliasline << std::endl;
286 //Keybindings.
287 for(auto i : lsnes_instance.mapper->get_bindings())
288 cfgfile << "BIND " << std::string(i) << " " << lsnes_instance.mapper->get(i) << std::endl;
289 //Buttons.
290 for(auto i : lsnes_instance.mapper->get_controller_keys()) {
291 std::string b;
292 unsigned idx = 0;
293 while((b = i->get_string(idx++)) != "")
294 cfgfile << "BUTTON " << b << " " << i->get_command() << std::endl;
296 for(auto i : lsnes_instance.buttons->button_keys)
297 cfgfile << "BUTTON " << i.second << " " << i.first << std::endl;
298 for(auto i : core_selections)
299 if(i.second != "")
300 cfgfile << "PREFER " << i.first << " " << i.second << std::endl;
301 //Sound config.
302 cfgfile << "AUDIO_PDEV " << platform::get_sound_device_description(false) << std::endl;
303 cfgfile << "AUDIO_RDEV " << platform::get_sound_device_description(true) << std::endl;
304 cfgfile << "AUDIO_GVOL " << to_logscale(lsnes_instance.audio->music_volume()) << std::endl;
305 cfgfile << "AUDIO_RVOL " << to_logscale(lsnes_instance.audio->voicer_volume()) << std::endl;
306 cfgfile << "AUDIO_PVOL " << to_logscale(lsnes_instance.audio->voicep_volume()) << std::endl;
307 cfgfile << "VIDEO_SFACT " << video_scale_factor << std::endl;
308 cfgfile << "VIDEO_SFLAGS " << scaling_flags << std::endl;
309 if(arcorrect_enabled) cfgfile << "VIDEO_ARC" << std::endl;
310 if(hflip_enabled) cfgfile << "VIDEO_HFLIP" << std::endl;
311 if(vflip_enabled) cfgfile << "VIDEO_VFLIP" << std::endl;
312 if(rotate_enabled) cfgfile << "VIDEO_ROTATE" << std::endl;
313 if(!cfgfile) {
314 show_message_ok(NULL, "Error Saving configuration", "Error saving configuration",
315 wxICON_EXCLAMATION);
316 return;
318 cfgfile.close();
319 directory::rename_overwrite(cfgtmp.c_str(), cfg.c_str());
320 //Last save.
321 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
322 lsave << last_save;
323 lsnes_uri_rewrite.save(get_config_path() + "/lsnesurirewrite.cfg");
326 void* eloop_helper(int x)
328 platform::dummy_event_loop();
329 return NULL;
332 std::string get_loaded_movie(const std::vector<std::string>& cmdline)
334 for(auto i : cmdline)
335 if(!i.empty() && i[0] != '-')
336 return i;
337 return "";
341 wxString towxstring(const std::string& str) throw(std::bad_alloc)
343 return wxString(str.c_str(), wxConvUTF8);
346 std::string tostdstring(const wxString& str) throw(std::bad_alloc)
348 return std::string(str.mb_str(wxConvUTF8));
351 wxString towxstring(const std::u32string& str) throw(std::bad_alloc)
353 return wxString(utf8::to8(str).c_str(), wxConvUTF8);
356 std::u32string tou32string(const wxString& str) throw(std::bad_alloc)
358 return utf8::to32(std::string(str.mb_str(wxConvUTF8)));
361 std::string pick_archive_member(wxWindow* parent, const std::string& filename) throw(std::bad_alloc)
363 CHECK_UI_THREAD;
364 //Did we pick a .zip file?
365 std::string f;
366 try {
367 zip::reader zr(filename);
368 std::vector<wxString> files;
369 for(auto i : zr)
370 files.push_back(towxstring(i));
371 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
372 wxT("Select member"), files.size(), &files[0]);
373 if(d2->ShowModal() == wxID_CANCEL) {
374 d2->Destroy();
375 return "";
377 f = filename + "/" + tostdstring(d2->GetStringSelection());
378 d2->Destroy();
379 } catch(...) {
380 //Ignore error.
381 f = filename;
383 return f;
386 void signal_program_exit()
388 post_ui_event(UISERV_EXIT);
391 void signal_resize_needed()
393 post_ui_event(UISERV_RESIZED);
397 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
398 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
399 wxCMD_LINE_PARAM_MULTIPLE },
400 { wxCMD_LINE_NONE }
403 class lsnes_app : public wxApp
405 public:
406 lsnes_app();
407 virtual bool OnInit();
408 virtual int OnExit();
409 virtual void OnInitCmdLine(wxCmdLineParser& parser);
410 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
411 private:
412 bool settings_mode;
413 bool pluginmanager_mode;
414 std::string c_rom;
415 std::string c_file;
416 std::vector<std::string> cmdline;
417 std::map<std::string, std::string> c_settings;
418 std::vector<std::string> c_lua;
419 std::vector<std::string> c_library;
420 bool exit_immediately;
421 bool fullscreen_mode;
422 bool start_unpaused;
423 struct dispatch::target<> screenupdate;
424 struct dispatch::target<> statusupdate;
425 struct dispatch::target<> actionupdate;
428 IMPLEMENT_APP(lsnes_app)
430 lsnes_app::lsnes_app()
432 settings_mode = false;
433 pluginmanager_mode = false;
434 exit_immediately = false;
435 fullscreen_mode = false;
436 start_unpaused = false;
439 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
441 parser.SetDesc(dummy_descriptor_table);
442 parser.SetSwitchChars(wxT(""));
445 static bool regex_sanity_check()
447 bool regex_sane = true;
448 try {
449 //Simple sanity checks.
450 regex_sane &= regex_match("foo.*baz", "foobarbaz", REGEX_MATCH_REGEX);
451 regex_sane &= regex_match(".*foo.*baz.*", "foobarbaz", REGEX_MATCH_REGEX);
452 regex_sane &= regex_match("foo*baz", "FOOBARBAZ", REGEX_MATCH_IWILDCARDS);
453 regex_sane &= regex_match("foo.*baz", "FOOBARBAZ", REGEX_MATCH_IREGEX);
454 } catch(...) {
455 regex_sane = false;
457 return regex_sane;
460 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
462 for(size_t i = 0; i< parser.GetParamCount(); i++)
463 cmdline.push_back(tostdstring(parser.GetParam(i)));
464 for(auto i: cmdline) {
465 regex_results r;
466 if(i == "--help" || i == "-h") {
467 std::cout << "--settings: Show the settings dialog" << std::endl;
468 std::cout << "--pluginmanager: Show the plugin manager" << std::endl;
469 std::cout << "--fullscreen: Start fullscreen" << std::endl;
470 std::cout << "--unpause: Start unpaused (only if ROM is loaded)" << std::endl;
471 std::cout << "--rom=<filename>: Load specified ROM on startup" << std::endl;
472 std::cout << "--load=<filename>: Load specified save/movie on starup" << std::endl;
473 std::cout << "--lua=<filename>: Load specified Lua script on startup" << std::endl;
474 std::cout << "--library=<filename>: Load specified library on startup" << std::endl;
475 std::cout << "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl;
476 std::cout << "--sanity-check: Perfrom some simple sanity checks" << std::endl;
477 std::cout << "<filename>: Load specified ROM on startup" << std::endl;
478 exit_immediately = true;
479 return true;
481 if(i == "--settings")
482 settings_mode = true;
483 if(i == "--unpause")
484 start_unpaused = true;
485 if(i == "--fullscreen")
486 fullscreen_mode = true;
487 if(i == "--pluginmanager")
488 pluginmanager_mode = true;
489 if(r = regex("--set=([^=]+)=(.+)", i))
490 c_settings[r[1]] = r[2];
491 if(r = regex("--lua=(.+)", i))
492 c_lua.push_back(r[1]);
493 if(r = regex("--library=(.+)", i))
494 c_library.push_back(r[1]);
495 if(i == "--sanity-check") {
496 if(regex_sanity_check()) {
497 std::cout << "Regex library passes basic sanity checks." << std::endl;
498 } else {
499 std::cout << "Regex library FAILS basic sanity checks." << std::endl;
501 std::cout << "Executable: '" << running_executable() << "'" << std::endl;
502 std::cout << "Configuration directory: '" << get_config_path()
503 << "'" << std::endl;
504 std::cout << "System autoload directory: '" << loadlib_debug_get_system_library_dir()
505 << "'" << std::endl;
506 std::cout << "User autoload directory: '" << loadlib_debug_get_user_library_dir()
507 << "'" << std::endl;
508 exit_immediately = true;
511 return true;
514 bool lsnes_app::OnInit()
516 wxApp::OnInit();
517 if(exit_immediately)
518 return false;
520 screenupdate.set(lsnes_instance.dispatch->screen_update, []() {
521 runuifun(screenupdate_once, []() {
522 if(main_window)
523 main_window->notify_update();
524 wxwindow_memorysearch_update(CORE());
525 wxwindow_tasinput_update(CORE());
528 statusupdate.set(lsnes_instance.dispatch->status_update, []() {
529 runuifun(statusupdate_once, []() {
530 if(main_window)
531 main_window->notify_update_status();
532 wxeditor_movie_update(CORE());
533 wxeditor_hexeditor_update(CORE());
536 actionupdate.set(lsnes_instance.dispatch->action_update, []() {
537 //This can be called early, so check for main_window existing.
538 if(main_window)
539 main_window->action_updated();
542 try {
543 crandom::init();
544 } catch(std::exception& e) {
545 show_message_ok(NULL, "RNG error", "Error initializing system RNG", wxICON_ERROR);
546 return false;
549 if(!regex_sanity_check()) {
550 wxMessageBox(towxstring("Regex sanity check FAILED.\n\nExpect problems."),
551 _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, NULL);
554 reached_main();
555 set_random_seed();
556 bring_app_foreground();
558 if(pluginmanager_mode)
559 if(!wxeditor_plugin_manager_display(NULL))
560 return false;
562 ui_services = new ui_services_type();
564 ui_thread = threads::this_id();
565 platform::init();
567 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
569 loaded_rom dummy_rom;
570 std::map<std::string, std::string> settings;
571 auto ctrldata = dummy_rom.controllerconfig(settings);
572 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
574 lsnes_instance.buttons->reinit();
575 lsnes_instance.controls->set_ports(ports);
577 std::string cfgpath = get_config_path();
578 autoload_libraries([](const std::string& libname, const std::string& error, bool system) {
579 show_message_ok(NULL, "Error loading plugin " + libname, "Error loading '" + libname + "'\n\n" +
580 error, wxICON_EXCLAMATION);
581 if(!system)
582 wxeditor_plugin_manager_notify_fail(libname);
584 messages << "Saving per-user data to: " << get_config_path() << std::endl;
585 messages << "--- Loading configuration --- " << std::endl;
586 load_configuration();
587 messages << "--- End running lsnesrc --- " << std::endl;
589 if(settings_mode) {
590 //We got to boot this up quite a bit to get the joystick driver working.
591 //In practicular, we need joystick thread and emulator thread in pause.
592 threads::thread* dummy_loop = new threads::thread(eloop_helper, 8);
593 display_settings_dialog(NULL, lsnes_instance, NULL);
594 platform::exit_dummy_event_loop();
595 joystick_driver_quit();
596 dummy_loop->join();
597 save_configuration();
598 return false;
600 init_lua(lsnes_instance);
601 lsnes_instance.mdumper->set_output(&messages.getstream());
603 msg_window = new wxwin_messages(lsnes_instance);
604 msg_window->Show();
606 init_main_callbacks();
608 //Load libraries before trying to load movie, in case there are cores in there.
609 for(auto i : c_library) {
610 try {
611 with_loaded_library(*new loadlib::module(loadlib::library(i)));
612 } catch(std::exception& e) {
613 show_message_ok(NULL, "Error loading library", std::string("Error loading library '") +
614 i + "':\n\n" + e.what(), wxICON_EXCLAMATION);
618 const std::string movie_file = get_loaded_movie(cmdline);
619 loaded_rom rom;
620 try {
621 moviefile mov;
622 rom = construct_rom(movie_file, cmdline);
623 rom.load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
624 } catch(std::exception& e) {
625 std::cerr << "Can't load ROM: " << e.what() << std::endl;
626 show_message_ok(NULL, "Error loading ROM", std::string("Error loading ROM:\n\n") +
627 e.what(), wxICON_EXCLAMATION);
628 quit_lua(lsnes_instance); //Don't crash.
629 return false;
632 moviefile* mov = NULL;
633 if(movie_file != "")
634 try {
635 mov = new moviefile(movie_file, rom.get_internal_rom_type());
636 rom.load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
637 } catch(std::exception& e) {
638 std::cerr << "Can't load state: " << e.what() << std::endl;
639 show_message_ok(NULL, "Error loading movie", std::string("Error loading movie:\n\n") +
640 e.what(), wxICON_EXCLAMATION);
641 quit_lua(lsnes_instance); //Don't crash.
642 return false;
644 else {
645 mov = new moviefile(rom, c_settings, DEFAULT_RTC_SECOND, DEFAULT_RTC_SUBSECOND);
647 *lsnes_instance.rom = rom;
648 mov->start_paused = start_unpaused ? rom.isnull() : true;
649 for(auto i : c_lua)
650 lsnes_instance.lua2->add_startup_script(i);
651 preboot_env = false;
652 boot_emulator(lsnes_instance, rom, *mov, fullscreen_mode);
653 return true;
656 int lsnes_app::OnExit()
658 if(settings_mode)
659 return 0;
660 //NULL these so no further messages will be sent.
661 auto x = msg_window;
662 msg_window = NULL;
663 main_window = NULL;
664 if(x)
665 x->Destroy();
666 save_configuration();
667 quit_lua(lsnes_instance);
668 lsnes_instance.mlogic->release_memory();
669 platform::quit();
670 lsnes_instance.buttons->cleanup();
671 cleanup_keymapper();
672 deinitialize_wx_mouse(lsnes_instance);
673 deinitialize_wx_keyboard(lsnes_instance);
674 return 0;
677 void do_save_configuration()
679 save_configuration();
682 namespace
684 struct _graphics_driver drv = {
685 .init = []() -> void {
686 initialize_wx_keyboard(lsnes_instance);
687 initialize_wx_mouse(lsnes_instance);
689 .quit = []() -> void {},
690 .notify_message = []() -> void
692 runuifun(message_once, []() {
693 if(msg_window)
694 msg_window->notify_update();
697 .error_message = [](const std::string& text) -> void {
698 error_message_text = text;
699 post_ui_event(UISERV_ERROR);
701 .fatal_error = []() -> void {
702 //Fun: This can be called from any thread!
703 if(ui_thread == threads::this_id()) {
704 //UI thread.
705 platform::set_modal_pause(true);
706 do_panic();
707 } else {
708 //Emulation thread panic. Signal the UI thread.
709 post_ui_event(UISERV_PANIC);
710 while(!panic_ack);
713 .name = []() -> const char* { return "wxwidgets graphics plugin"; },
714 .request_rom = [](rom_request& req)
716 rom_request* _req = &req;
717 threads::lock lock;
718 threads::cv cv;
719 bool done = false;
720 if(preboot_env) {
721 try {
722 //main_window is NULL, hope this does not crash.
723 main_window->request_rom(*_req);
724 } catch(...) {
725 _req->canceled = true;
727 return;
729 threads::alock h(lock);
730 runuifun([_req, &lock, &cv, &done]() -> void {
731 try {
732 main_window->request_rom(*_req);
733 } catch(...) {
734 _req->canceled = true;
736 threads::alock h(lock);
737 done = true;
738 cv.notify_all();
740 while(!done)
741 cv.wait(h);
744 struct graphics_driver _drv(drv);
747 void signal_core_change()
749 post_ui_event(UISERV_REFRESH_TITLE);
752 void _runuifun_async(runuifun_once_ctx* ctx, void (*fn)(void*), void* arg)
754 if(ctx && !ctx->set_flag()) return;
755 threads::alock h(ui_mutex);
756 ui_queue_entry e;
757 e.fn = fn;
758 e.arg = arg;
759 e.ctx = ctx;
760 ui_queue.push_back(e);
761 post_ui_event(UISERV_UIFUN);
765 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
767 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir)
769 CHECK_UI_THREAD;
770 wxString _title = towxstring(title);
771 wxString _startdir = towxstring(startdir);
772 std::string filespec;
773 filespec = "All files|*";
774 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), towxstring(filespec), wxFD_OPEN);
775 if(d->ShowModal() == wxID_CANCEL)
776 throw canceled_exception();
777 std::string filename = tostdstring(d->GetPath());
778 d->Destroy();
779 if(filename == "")
780 throw canceled_exception();
781 return filename;
784 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
786 CHECK_UI_THREAD;
787 std::string filename = pick_file(parent, title, startdir);
788 //Did we pick a .zip file?
789 if(!regex_match(".*\\.[zZ][iI][pP]", filename))
790 return filename; //Not a ZIP.
791 try {
792 zip::reader zr(filename);
793 std::vector<std::string> files;
794 for(auto i : zr)
795 files.push_back(i);
796 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
797 } catch(canceled_exception& e) {
798 //Throw these forward.
799 throw;
800 } catch(...) {
801 //Ignore error.
803 return filename;
806 unsigned pick_among_index(wxWindow* parent, const std::string& title, const std::string& prompt,
807 const std::vector<std::string>& choices, unsigned defaultchoice)
809 CHECK_UI_THREAD;
810 std::vector<wxString> _choices;
811 for(auto i : choices)
812 _choices.push_back(towxstring(i));
813 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
814 _choices.size(), &_choices[0]);
815 d2->SetSelection(defaultchoice);
816 if(d2->ShowModal() == wxID_CANCEL) {
817 d2->Destroy();
818 throw canceled_exception();
820 unsigned idx = d2->GetSelection();
821 d2->Destroy();
822 return idx;
825 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
826 const std::vector<std::string>& choices, unsigned defaultchoice)
828 unsigned idx = pick_among_index(parent, title, prompt, choices, defaultchoice);
829 if(idx < choices.size())
830 return choices[idx];
831 throw canceled_exception();
834 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
835 bool multiline)
837 CHECK_UI_THREAD;
838 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
839 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
840 if(d2->ShowModal() == wxID_CANCEL) {
841 d2->Destroy();
842 throw canceled_exception();
844 std::string text = tostdstring(d2->GetValue());
845 d2->Destroy();
846 return text;
849 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
851 CHECK_UI_THREAD;
852 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
853 d3->ShowModal();
854 d3->Destroy();
857 bool run_show_error(wxWindow* parent, const std::string& title, const std::string& text, std::function<void()> fn)
859 try {
860 fn();
861 return false;
862 } catch(std::exception& e) {
863 std::string err = e.what();
864 std::string _title = title;
865 std::string _text = (text == "") ? err : (text + ": " + err);
866 runuifun([parent, _title, _text]() {
867 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
869 return true;
873 void show_exception(wxWindow* parent, const std::string& title, const std::string& text, std::exception& e)
875 CHECK_UI_THREAD;
876 std::string err = e.what();
877 std::string _title = title;
878 std::string _text = (text == "") ? err : (text + ": " + err);
879 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
882 void show_exception_any(wxWindow* parent, const std::string& title, const std::string& text, std::exception& e)
884 std::string err = e.what();
885 std::string _title = title;
886 std::string _text = (text == "") ? err : (text + ": " + err);
887 runuifun([parent, _title, _text]() {
888 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
892 void _check_ui_thread(const char* file, int line)
894 if(ui_thread == threads::this_id())
895 return;
896 std::cerr << "UI routine running in wrong thread at " << file << ":" << line << std::endl;