Add <functional> to files that use std::function
[lsnes.git] / src / platform / wxwidgets / main.cpp
blob42506a6762cc0ed064dafd7b5b32a38fc7660261
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 #define UISERV_REFRESH_TITLE 9990
53 #define UISERV_RESIZED 9991
54 #define UISERV_UIFUN 9992
55 #define UISERV_EXIT 9994
56 #define UISERV_PANIC 9998
57 #define UISERV_ERROR 9999
59 wxwin_messages* msg_window;
60 wxwin_mainwindow* main_window;
61 std::string our_rom_name;
63 bool wxwidgets_exiting = false;
65 namespace
67 threads::id ui_thread;
68 volatile bool panic_ack = false;
69 std::string error_message_text;
70 volatile bool modal_dialog_confirm;
71 volatile bool modal_dialog_active;
72 threads::lock ui_mutex;
73 threads::cv ui_condition;
74 bool preboot_env = true;
75 runuifun_once_ctx screenupdate_once;
76 runuifun_once_ctx statusupdate_once;
77 runuifun_once_ctx message_once;
79 struct uiserv_event : public wxEvent
81 uiserv_event(int code)
83 SetId(code);
86 wxEvent* Clone() const
88 return new uiserv_event(*this);
92 class ui_services_type : public wxEvtHandler
94 bool ProcessEvent(wxEvent& event);
97 struct ui_queue_entry
99 void(*fn)(void*);
100 void* arg;
101 runuifun_once_ctx* ctx;
104 std::list<ui_queue_entry> ui_queue;
106 void do_panic()
108 std::string msg = "Panic: Unrecoverable error, can't continue";
109 std::string msg2 = platform::msgbuf.get_last_message();
110 if(msg2 != "")
111 msg += "\n\n" + msg2;
112 wxMessageBox(towxstring(msg), _T("Error"), wxICON_ERROR | wxOK);
115 bool ui_services_type::ProcessEvent(wxEvent& event)
117 CHECK_UI_THREAD;
118 int c = event.GetId();
119 if(c == UISERV_PANIC) {
120 //We need to panic.
121 do_panic();
122 panic_ack = true;
123 } else if(c == UISERV_REFRESH_TITLE) {
124 if(main_window)
125 main_window->refresh_title();
126 } else if(c == UISERV_RESIZED) {
127 if(main_window)
128 main_window->notify_resized();
129 } else if(c == UISERV_ERROR) {
130 std::string text = error_message_text;
131 wxMessageBox(towxstring(text), _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, main_window);
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 threads::alock 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 if(e.ctx) e.ctx->clear_flag();
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 std::string loaded_pdev;
166 std::string loaded_rdev;
168 double from_logscale(double v)
170 return exp(v);
173 void handle_config_line(std::string line)
175 regex_results r;
176 if(r = regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
177 lsnes_instance.setcache->set(r[1], r[2], true);
178 messages << "Setting " << r[1] << " set to " << r[2] << std::endl;
179 } else if(r = regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
180 if(!lsnes_instance.command->valid_alias_name(r[1])) {
181 messages << "Illegal alias name " << r[1] << std::endl;
182 return;
184 std::string tmp = lsnes_instance.command->get_alias_for(r[1]);
185 tmp = tmp + r[2] + "\n";
186 lsnes_instance.command->set_alias_for(r[1], tmp);
187 messages << r[1] << " aliased to " << r[2] << std::endl;
188 } else if(r = regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line)) {
189 std::string tmp = r[4];
190 regex_results r2 = regex("(load|load-smart|load-readonly|load-preserve|load-state"
191 "|load-movie|save-state|save-movie)[ \t]+\\$\\{project\\}(.*)\\.lsmv", tmp);
192 if(r2) tmp = r2[1] + " $SLOT:" + r2[2];
193 lsnes_instance.mapper->bind(r[1], r[2], r[3], tmp);
194 if(r[1] != "" || r[2] != "")
195 messages << r[1] << "/" << r[2] << " ";
196 messages << r[3] << " bound to '" << tmp << "'" << std::endl;
197 } else if(r = regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line)) {
198 keyboard::ctrlrkey* ckey = lsnes_instance.mapper->get_controllerkey(r[2]);
199 if(ckey) {
200 ckey->append(r[1]);
201 messages << r[1] << " bound (button) to " << r[2] << std::endl;
202 } else
203 lsnes_instance.buttons->button_keys[r[2]] = r[1];
204 } else if(r = regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
205 if(r[2] != "") {
206 core_selections[r[1]] = r[2];
207 messages << "Prefer " << r[2] << " for " << r[1] << std::endl;
209 } else if(r = regex("AUDIO_PDEV[ \t]+([^ \t].*)", line)) {
210 loaded_pdev = r[1];
211 } else if(r = regex("AUDIO_RDEV[ \t]+([^ \t].*)", line)) {
212 loaded_rdev = r[1];
213 } else if(r = regex("AUDIO_GVOL[ \t]+([^ \t].*)", line)) {
214 lsnes_instance.audio->music_volume(from_logscale(parse_value<double>(r[1])));
215 } else if(r = regex("AUDIO_RVOL[ \t]+([^ \t].*)", line)) {
216 lsnes_instance.audio->voicer_volume(from_logscale(parse_value<double>(r[1])));
217 } else if(r = regex("AUDIO_PVOL[ \t]+([^ \t].*)", line)) {
218 lsnes_instance.audio->voicep_volume(from_logscale(parse_value<double>(r[1])));
219 } else if(r = regex("VIDEO_ARC[ \t]*", line)) {
220 arcorrect_enabled = true;
221 } else if(r = regex("VIDEO_HFLIP[ \t]*", line)) {
222 hflip_enabled = true;
223 } else if(r = regex("VIDEO_VFLIP[ \t]*", line)) {
224 vflip_enabled = true;
225 } else if(r = regex("VIDEO_ROTATE[ \t]*", line)) {
226 rotate_enabled = true;
227 } else if(r = regex("VIDEO_SFACT[ \t]+([^ \t].*)", line)) {
228 double val = parse_value<double>(r[1]);
229 if(val < 0.1 || val > 10) throw std::runtime_error("Crazy scale factor");
230 video_scale_factor = val;
231 } else if(r = regex("VIDEO_SFLAGS[ \t]+([^ \t].*)", line)) {
232 scaling_flags = parse_value<uint32_t>(r[1]);
233 } else
234 (stringfmt() << "Unrecognized directive: " << line).throwex();
237 void load_configuration()
239 std::string cfg = get_config_path() + "/lsneswxw.cfg";
240 std::ifstream cfgfile(cfg.c_str());
241 std::string line;
242 size_t lineno = 1;
243 while(std::getline(cfgfile, line)) {
244 try {
245 handle_config_line(line);
246 } catch(std::exception& e) {
247 messages << "Error processing line " << lineno << ": " << e.what() << std::endl;
249 lineno++;
251 platform::set_sound_device_by_description(loaded_pdev, loaded_rdev);
252 (*lsnes_instance.abindmanager)();
253 lsnes_uri_rewrite.load(get_config_path() + "/lsnesurirewrite.cfg");
256 double to_logscale(double v)
258 if(fabs(v) < 1e-15)
259 return -999.0;
260 return log(fabs(v));
263 void save_configuration()
265 std::string cfg = get_config_path() + "/lsneswxw.cfg";
266 std::string cfgtmp = cfg + ".tmp";
267 std::ofstream cfgfile(cfgtmp.c_str());
268 //Settings.
269 for(auto i : lsnes_instance.setcache->get_all())
270 cfgfile << "SET " << i.first << " " << i.second << std::endl;
271 //Aliases.
272 for(auto i : lsnes_instance.command->get_aliases()) {
273 std::string old_alias_value = lsnes_instance.command->get_alias_for(i);
274 while(old_alias_value != "") {
275 std::string aliasline;
276 size_t s = old_alias_value.find_first_of("\n");
277 if(s < old_alias_value.length()) {
278 aliasline = old_alias_value.substr(0, s);
279 old_alias_value = old_alias_value.substr(s + 1);
280 } else {
281 aliasline = old_alias_value;
282 old_alias_value = "";
284 cfgfile << "ALIAS " << i << " " << aliasline << std::endl;
287 //Keybindings.
288 for(auto i : lsnes_instance.mapper->get_bindings())
289 cfgfile << "BIND " << std::string(i) << " " << lsnes_instance.mapper->get(i) << std::endl;
290 //Buttons.
291 for(auto i : lsnes_instance.mapper->get_controller_keys()) {
292 std::string b;
293 unsigned idx = 0;
294 while((b = i->get_string(idx++)) != "")
295 cfgfile << "BUTTON " << b << " " << i->get_command() << std::endl;
297 for(auto i : lsnes_instance.buttons->button_keys)
298 cfgfile << "BUTTON " << i.second << " " << i.first << std::endl;
299 for(auto i : core_selections)
300 if(i.second != "")
301 cfgfile << "PREFER " << i.first << " " << i.second << std::endl;
302 //Sound config.
303 cfgfile << "AUDIO_PDEV " << platform::get_sound_device_description(false) << std::endl;
304 cfgfile << "AUDIO_RDEV " << platform::get_sound_device_description(true) << std::endl;
305 cfgfile << "AUDIO_GVOL " << to_logscale(lsnes_instance.audio->music_volume()) << std::endl;
306 cfgfile << "AUDIO_RVOL " << to_logscale(lsnes_instance.audio->voicer_volume()) << std::endl;
307 cfgfile << "AUDIO_PVOL " << to_logscale(lsnes_instance.audio->voicep_volume()) << std::endl;
308 cfgfile << "VIDEO_SFACT " << video_scale_factor << std::endl;
309 cfgfile << "VIDEO_SFLAGS " << scaling_flags << std::endl;
310 if(arcorrect_enabled) cfgfile << "VIDEO_ARC" << std::endl;
311 if(hflip_enabled) cfgfile << "VIDEO_HFLIP" << std::endl;
312 if(vflip_enabled) cfgfile << "VIDEO_VFLIP" << std::endl;
313 if(rotate_enabled) cfgfile << "VIDEO_ROTATE" << std::endl;
314 if(!cfgfile) {
315 show_message_ok(NULL, "Error Saving configuration", "Error saving configuration",
316 wxICON_EXCLAMATION);
317 return;
319 cfgfile.close();
320 directory::rename_overwrite(cfgtmp.c_str(), cfg.c_str());
321 //Last save.
322 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
323 lsave << last_save;
324 lsnes_uri_rewrite.save(get_config_path() + "/lsnesurirewrite.cfg");
327 void* eloop_helper(int x)
329 platform::dummy_event_loop();
330 return NULL;
333 std::string get_loaded_movie(const std::vector<std::string>& cmdline)
335 for(auto i : cmdline)
336 if(!i.empty() && i[0] != '-')
337 return i;
338 return "";
342 wxString towxstring(const std::string& str) throw(std::bad_alloc)
344 return wxString(str.c_str(), wxConvUTF8);
347 std::string tostdstring(const wxString& str) throw(std::bad_alloc)
349 return std::string(str.mb_str(wxConvUTF8));
352 wxString towxstring(const std::u32string& str) throw(std::bad_alloc)
354 return wxString(utf8::to8(str).c_str(), wxConvUTF8);
357 std::u32string tou32string(const wxString& str) throw(std::bad_alloc)
359 return utf8::to32(std::string(str.mb_str(wxConvUTF8)));
362 std::string pick_archive_member(wxWindow* parent, const std::string& filename) throw(std::bad_alloc)
364 CHECK_UI_THREAD;
365 //Did we pick a .zip file?
366 std::string f;
367 try {
368 zip::reader zr(filename);
369 std::vector<wxString> files;
370 for(auto i : zr)
371 files.push_back(towxstring(i));
372 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
373 wxT("Select member"), files.size(), &files[0]);
374 if(d2->ShowModal() == wxID_CANCEL) {
375 d2->Destroy();
376 return "";
378 f = filename + "/" + tostdstring(d2->GetStringSelection());
379 d2->Destroy();
380 } catch(...) {
381 //Ignore error.
382 f = filename;
384 return f;
387 void signal_program_exit()
389 post_ui_event(UISERV_EXIT);
392 void signal_resize_needed()
394 post_ui_event(UISERV_RESIZED);
398 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
399 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
400 wxCMD_LINE_PARAM_MULTIPLE },
401 { wxCMD_LINE_NONE }
404 class lsnes_app : public wxApp
406 public:
407 lsnes_app();
408 virtual bool OnInit();
409 virtual int OnExit();
410 virtual void OnInitCmdLine(wxCmdLineParser& parser);
411 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
412 private:
413 bool settings_mode;
414 bool pluginmanager_mode;
415 std::string c_rom;
416 std::string c_file;
417 std::vector<std::string> cmdline;
418 std::map<std::string, std::string> c_settings;
419 std::vector<std::string> c_lua;
420 std::vector<std::string> c_library;
421 bool exit_immediately;
422 bool fullscreen_mode;
423 bool start_unpaused;
424 struct dispatch::target<> screenupdate;
425 struct dispatch::target<> statusupdate;
426 struct dispatch::target<> actionupdate;
429 IMPLEMENT_APP(lsnes_app)
431 lsnes_app::lsnes_app()
433 settings_mode = false;
434 pluginmanager_mode = false;
435 exit_immediately = false;
436 fullscreen_mode = false;
437 start_unpaused = false;
440 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
442 parser.SetDesc(dummy_descriptor_table);
443 parser.SetSwitchChars(wxT(""));
446 static bool regex_sanity_check()
448 bool regex_sane = true;
449 try {
450 //Simple sanity checks.
451 regex_sane &= regex_match("foo.*baz", "foobarbaz", REGEX_MATCH_REGEX);
452 regex_sane &= regex_match(".*foo.*baz.*", "foobarbaz", REGEX_MATCH_REGEX);
453 regex_sane &= regex_match("foo*baz", "FOOBARBAZ", REGEX_MATCH_IWILDCARDS);
454 regex_sane &= regex_match("foo.*baz", "FOOBARBAZ", REGEX_MATCH_IREGEX);
455 } catch(...) {
456 regex_sane = false;
458 return regex_sane;
461 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
463 for(size_t i = 0; i< parser.GetParamCount(); i++)
464 cmdline.push_back(tostdstring(parser.GetParam(i)));
465 for(auto i: cmdline) {
466 regex_results r;
467 if(i == "--help" || i == "-h") {
468 std::cout << "--settings: Show the settings dialog" << std::endl;
469 std::cout << "--pluginmanager: Show the plugin manager" << std::endl;
470 std::cout << "--fullscreen: Start fullscreen" << std::endl;
471 std::cout << "--unpause: Start unpaused (only if ROM is loaded)" << std::endl;
472 std::cout << "--rom=<filename>: Load specified ROM on startup" << std::endl;
473 std::cout << "--load=<filename>: Load specified save/movie on starup" << std::endl;
474 std::cout << "--lua=<filename>: Load specified Lua script on startup" << std::endl;
475 std::cout << "--library=<filename>: Load specified library on startup" << std::endl;
476 std::cout << "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl;
477 std::cout << "--sanity-check: Perfrom some simple sanity checks" << std::endl;
478 std::cout << "<filename>: Load specified ROM on startup" << std::endl;
479 exit_immediately = true;
480 return true;
482 if(i == "--settings")
483 settings_mode = true;
484 if(i == "--unpause")
485 start_unpaused = true;
486 if(i == "--fullscreen")
487 fullscreen_mode = true;
488 if(i == "--pluginmanager")
489 pluginmanager_mode = true;
490 if(r = regex("--set=([^=]+)=(.+)", i))
491 c_settings[r[1]] = r[2];
492 if(r = regex("--lua=(.+)", i))
493 c_lua.push_back(r[1]);
494 if(r = regex("--library=(.+)", i))
495 c_library.push_back(r[1]);
496 if(i == "--sanity-check") {
497 if(regex_sanity_check()) {
498 std::cout << "Regex library passes basic sanity checks." << std::endl;
499 } else {
500 std::cout << "Regex library FAILS basic sanity checks." << std::endl;
502 std::cout << "Executable: '" << running_executable() << "'" << std::endl;
503 std::cout << "Configuration directory: '" << get_config_path()
504 << "'" << std::endl;
505 std::cout << "System autoload directory: '" << loadlib_debug_get_system_library_dir()
506 << "'" << std::endl;
507 std::cout << "User autoload directory: '" << loadlib_debug_get_user_library_dir()
508 << "'" << std::endl;
509 exit_immediately = true;
512 return true;
515 bool lsnes_app::OnInit()
517 wxApp::OnInit();
518 if(exit_immediately)
519 return false;
521 screenupdate.set(lsnes_instance.dispatch->screen_update, []() {
522 runuifun(screenupdate_once, []() {
523 if(main_window)
524 main_window->notify_update();
525 wxwindow_memorysearch_update(CORE());
526 wxwindow_tasinput_update(CORE());
529 statusupdate.set(lsnes_instance.dispatch->status_update, []() {
530 runuifun(statusupdate_once, []() {
531 if(main_window)
532 main_window->notify_update_status();
533 wxeditor_movie_update(CORE());
534 wxeditor_hexeditor_update(CORE());
537 actionupdate.set(lsnes_instance.dispatch->action_update, []() {
538 //This can be called early, so check for main_window existing.
539 if(main_window)
540 main_window->action_updated();
543 try {
544 crandom::init();
545 } catch(std::exception& e) {
546 show_message_ok(NULL, "RNG error", "Error initializing system RNG", wxICON_ERROR);
547 return false;
550 if(!regex_sanity_check()) {
551 wxMessageBox(towxstring("Regex sanity check FAILED.\n\nExpect problems."),
552 _T("lsnes: Error"), wxICON_EXCLAMATION | wxOK, NULL);
555 reached_main();
556 set_random_seed();
557 bring_app_foreground();
559 if(pluginmanager_mode)
560 if(!wxeditor_plugin_manager_display(NULL))
561 return false;
563 ui_services = new ui_services_type();
565 ui_thread = threads::this_id();
566 platform::init();
568 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
570 loaded_rom dummy_rom;
571 std::map<std::string, std::string> settings;
572 auto ctrldata = dummy_rom.controllerconfig(settings);
573 portctrl::type_set& ports = portctrl::type_set::make(ctrldata.ports, ctrldata.portindex());
575 lsnes_instance.buttons->reinit();
576 lsnes_instance.controls->set_ports(ports);
578 std::string cfgpath = get_config_path();
579 autoload_libraries([](const std::string& libname, const std::string& error, bool system) {
580 show_message_ok(NULL, "Error loading plugin " + libname, "Error loading '" + libname + "'\n\n" +
581 error, wxICON_EXCLAMATION);
582 if(!system)
583 wxeditor_plugin_manager_notify_fail(libname);
585 messages << "Saving per-user data to: " << get_config_path() << std::endl;
586 messages << "--- Loading configuration --- " << std::endl;
587 load_configuration();
588 messages << "--- End running lsnesrc --- " << std::endl;
590 if(settings_mode) {
591 //We got to boot this up quite a bit to get the joystick driver working.
592 //In practicular, we need joystick thread and emulator thread in pause.
593 threads::thread* dummy_loop = new threads::thread(eloop_helper, 8);
594 display_settings_dialog(NULL, lsnes_instance, NULL);
595 platform::exit_dummy_event_loop();
596 joystick_driver_quit();
597 dummy_loop->join();
598 save_configuration();
599 return false;
601 init_lua(lsnes_instance);
602 lsnes_instance.mdumper->set_output(&messages.getstream());
604 msg_window = new wxwin_messages(lsnes_instance);
605 msg_window->Show();
607 init_main_callbacks();
609 //Load libraries before trying to load movie, in case there are cores in there.
610 for(auto i : c_library) {
611 try {
612 with_loaded_library(*new loadlib::module(loadlib::library(i)));
613 } catch(std::exception& e) {
614 show_message_ok(NULL, "Error loading library", std::string("Error loading library '") +
615 i + "':\n\n" + e.what(), wxICON_EXCLAMATION);
619 const std::string movie_file = get_loaded_movie(cmdline);
620 loaded_rom rom;
621 try {
622 moviefile mov;
623 rom = construct_rom(movie_file, cmdline);
624 rom.load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
625 } catch(std::exception& e) {
626 std::cerr << "Can't load ROM: " << e.what() << std::endl;
627 show_message_ok(NULL, "Error loading ROM", std::string("Error loading ROM:\n\n") +
628 e.what(), wxICON_EXCLAMATION);
629 quit_lua(lsnes_instance); //Don't crash.
630 return false;
633 moviefile* mov = NULL;
634 if(movie_file != "")
635 try {
636 mov = new moviefile(movie_file, rom.get_internal_rom_type());
637 rom.load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
638 } catch(std::exception& e) {
639 std::cerr << "Can't load state: " << e.what() << std::endl;
640 show_message_ok(NULL, "Error loading movie", std::string("Error loading movie:\n\n") +
641 e.what(), wxICON_EXCLAMATION);
642 quit_lua(lsnes_instance); //Don't crash.
643 return false;
645 else {
646 mov = new moviefile(rom, c_settings, DEFAULT_RTC_SECOND, DEFAULT_RTC_SUBSECOND);
648 *lsnes_instance.rom = rom;
649 mov->start_paused = start_unpaused ? rom.isnull() : true;
650 for(auto i : c_lua)
651 lsnes_instance.lua2->add_startup_script(i);
652 preboot_env = false;
653 boot_emulator(lsnes_instance, rom, *mov, fullscreen_mode);
654 return true;
657 int lsnes_app::OnExit()
659 if(settings_mode)
660 return 0;
661 //NULL these so no further messages will be sent.
662 auto x = msg_window;
663 msg_window = NULL;
664 main_window = NULL;
665 if(x)
666 x->Destroy();
667 save_configuration();
668 quit_lua(lsnes_instance);
669 lsnes_instance.mlogic->release_memory();
670 platform::quit();
671 lsnes_instance.buttons->cleanup();
672 cleanup_keymapper();
673 deinitialize_wx_mouse(lsnes_instance);
674 deinitialize_wx_keyboard(lsnes_instance);
675 return 0;
678 void do_save_configuration()
680 save_configuration();
683 namespace
685 struct _graphics_driver drv = {
686 .init = []() -> void {
687 initialize_wx_keyboard(lsnes_instance);
688 initialize_wx_mouse(lsnes_instance);
690 .quit = []() -> void {},
691 .notify_message = []() -> void
693 runuifun(message_once, []() {
694 if(msg_window)
695 msg_window->notify_update();
698 .error_message = [](const std::string& text) -> void {
699 error_message_text = text;
700 post_ui_event(UISERV_ERROR);
702 .fatal_error = []() -> void {
703 //Fun: This can be called from any thread!
704 if(ui_thread == threads::this_id()) {
705 //UI thread.
706 platform::set_modal_pause(true);
707 do_panic();
708 } else {
709 //Emulation thread panic. Signal the UI thread.
710 post_ui_event(UISERV_PANIC);
711 while(!panic_ack);
714 .name = []() -> const char* { return "wxwidgets graphics plugin"; },
715 .request_rom = [](rom_request& req)
717 rom_request* _req = &req;
718 threads::lock lock;
719 threads::cv cv;
720 bool done = false;
721 if(preboot_env) {
722 try {
723 //main_window is NULL, hope this does not crash.
724 main_window->request_rom(*_req);
725 } catch(...) {
726 _req->canceled = true;
728 return;
730 threads::alock h(lock);
731 runuifun([_req, &lock, &cv, &done]() -> void {
732 try {
733 main_window->request_rom(*_req);
734 } catch(...) {
735 _req->canceled = true;
737 threads::alock h(lock);
738 done = true;
739 cv.notify_all();
741 while(!done)
742 cv.wait(h);
745 struct graphics_driver _drv(drv);
748 void signal_core_change()
750 post_ui_event(UISERV_REFRESH_TITLE);
753 void _runuifun_async(runuifun_once_ctx* ctx, void (*fn)(void*), void* arg)
755 if(ctx && !ctx->set_flag()) return;
756 threads::alock h(ui_mutex);
757 ui_queue_entry e;
758 e.fn = fn;
759 e.arg = arg;
760 e.ctx = ctx;
761 ui_queue.push_back(e);
762 post_ui_event(UISERV_UIFUN);
766 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
768 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir)
770 CHECK_UI_THREAD;
771 wxString _title = towxstring(title);
772 wxString _startdir = towxstring(startdir);
773 std::string filespec;
774 filespec = "All files|*";
775 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), towxstring(filespec), wxFD_OPEN);
776 if(d->ShowModal() == wxID_CANCEL)
777 throw canceled_exception();
778 std::string filename = tostdstring(d->GetPath());
779 d->Destroy();
780 if(filename == "")
781 throw canceled_exception();
782 return filename;
785 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
787 CHECK_UI_THREAD;
788 std::string filename = pick_file(parent, title, startdir);
789 //Did we pick a .zip file?
790 if(!regex_match(".*\\.[zZ][iI][pP]", filename))
791 return filename; //Not a ZIP.
792 try {
793 zip::reader zr(filename);
794 std::vector<std::string> files;
795 for(auto i : zr)
796 files.push_back(i);
797 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
798 } catch(canceled_exception& e) {
799 //Throw these forward.
800 throw;
801 } catch(...) {
802 //Ignore error.
804 return filename;
807 unsigned pick_among_index(wxWindow* parent, const std::string& title, const std::string& prompt,
808 const std::vector<std::string>& choices, unsigned defaultchoice)
810 CHECK_UI_THREAD;
811 std::vector<wxString> _choices;
812 for(auto i : choices)
813 _choices.push_back(towxstring(i));
814 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
815 _choices.size(), &_choices[0]);
816 d2->SetSelection(defaultchoice);
817 if(d2->ShowModal() == wxID_CANCEL) {
818 d2->Destroy();
819 throw canceled_exception();
821 unsigned idx = d2->GetSelection();
822 d2->Destroy();
823 return idx;
826 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
827 const std::vector<std::string>& choices, unsigned defaultchoice)
829 unsigned idx = pick_among_index(parent, title, prompt, choices, defaultchoice);
830 if(idx < choices.size())
831 return choices[idx];
832 throw canceled_exception();
835 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
836 bool multiline)
838 CHECK_UI_THREAD;
839 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
840 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
841 if(d2->ShowModal() == wxID_CANCEL) {
842 d2->Destroy();
843 throw canceled_exception();
845 std::string text = tostdstring(d2->GetValue());
846 d2->Destroy();
847 return text;
850 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
852 CHECK_UI_THREAD;
853 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
854 d3->ShowModal();
855 d3->Destroy();
858 bool run_show_error(wxWindow* parent, const std::string& title, const std::string& text, std::function<void()> fn)
860 try {
861 fn();
862 return false;
863 } catch(std::exception& e) {
864 std::string err = e.what();
865 std::string _title = title;
866 std::string _text = (text == "") ? err : (text + ": " + err);
867 runuifun([parent, _title, _text]() {
868 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
870 return true;
874 void show_exception(wxWindow* parent, const std::string& title, const std::string& text, std::exception& e)
876 CHECK_UI_THREAD;
877 std::string err = e.what();
878 std::string _title = title;
879 std::string _text = (text == "") ? err : (text + ": " + err);
880 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
883 void show_exception_any(wxWindow* parent, const std::string& title, const std::string& text, std::exception& e)
885 std::string err = e.what();
886 std::string _title = title;
887 std::string _text = (text == "") ? err : (text + ": " + err);
888 runuifun([parent, _title, _text]() {
889 show_message_ok(parent, _title, _text, wxICON_EXCLAMATION);
893 void _check_ui_thread(const char* file, int line)
895 if(ui_thread == threads::this_id())
896 return;
897 std::cerr << "UI routine running in wrong thread at " << file << ":" << line << std::endl;