Show dialog if loading ROM/movie from command line fails
[lsnes.git] / src / platform / wxwidgets / main.cpp
blob58b191859acfe0067de15638260936949ee6268c
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;
361 IMPLEMENT_APP(lsnes_app)
363 lsnes_app::lsnes_app()
365 settings_mode = false;
366 pluginmanager_mode = false;
367 exit_immediately = false;
370 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
372 parser.SetDesc(dummy_descriptor_table);
373 parser.SetSwitchChars(wxT(""));
376 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
378 for(size_t i = 0; i< parser.GetParamCount(); i++)
379 cmdline.push_back(tostdstring(parser.GetParam(i)));
380 for(auto i: cmdline) {
381 regex_results r;
382 if(i == "--help" || i == "-h") {
383 std::cout << "--settings: Show the settings dialog" << std::endl;
384 std::cout << "--pluginmanager: Show the plugin manager" << std::endl;
385 std::cout << "--rom=<filename>: Load specified ROM on startup" << std::endl;
386 std::cout << "--load=<filename>: Load specified save/movie on starup" << std::endl;
387 std::cout << "--lua=<filename>: Load specified Lua script on startup" << std::endl;
388 std::cout << "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl;
389 std::cout << "<filename>: Load specified ROM on startup" << std::endl;
390 exit_immediately = true;
391 return true;
393 if(i == "--settings")
394 settings_mode = true;
395 if(i == "--pluginmanager")
396 pluginmanager_mode = true;
397 if(r = regex("--set=([^=]+)=(.+)", i))
398 c_settings[r[1]] = r[2];
399 if(r = regex("--lua=(.+)", i))
400 c_lua.push_back(r[1]);
402 return true;
405 bool lsnes_app::OnInit()
407 wxApp::OnInit();
408 if(exit_immediately)
409 return false;
411 reached_main();
412 set_random_seed();
413 bring_app_foreground();
415 if(pluginmanager_mode)
416 if(!wxeditor_plugin_manager_display(NULL))
417 return false;
419 ui_services = new ui_services_type();
421 ui_thread = this_thread_id();
422 platform::init();
424 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
426 loaded_rom dummy_rom;
427 std::map<std::string, std::string> settings;
428 auto ctrldata = dummy_rom.rtype->controllerconfig(settings);
429 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex());
431 reinitialize_buttonmap();
432 controls.set_ports(ports);
434 std::string cfgpath = get_config_path();
435 autoload_libraries([](const std::string& libname, const std::string& error, bool system) {
436 show_message_ok(NULL, "Error loading plugin " + libname, "Error loading '" + libname + "'\n\n" +
437 error, wxICON_EXCLAMATION);
438 if(!system)
439 wxeditor_plugin_manager_notify_fail(libname);
441 messages << "Saving per-user data to: " << get_config_path() << std::endl;
442 messages << "--- Loading configuration --- " << std::endl;
443 load_configuration();
444 messages << "--- End running lsnesrc --- " << std::endl;
446 if(settings_mode) {
447 //We got to boot this up quite a bit to get the joystick driver working.
448 //In practicular, we need joystick thread and emulator thread in pause.
449 joystick_thread_handle = new thread_class(joystick_thread, 6);
450 thread_class* dummy_loop = new thread_class(eloop_helper, 8);
451 display_settings_dialog(NULL, NULL);
452 platform::exit_dummy_event_loop();
453 joystick_driver_signal();
454 joystick_thread_handle->join();
455 dummy_loop->join();
456 save_configuration();
457 return false;
459 init_lua();
461 joystick_thread_handle = new thread_class(joystick_thread, 7);
463 msg_window = new wxwin_messages();
464 msg_window->Show();
466 const std::string movie_file = get_loaded_movie(cmdline);
467 loaded_rom rom;
468 try {
469 moviefile mov;
470 rom = construct_rom(movie_file, cmdline);
471 rom.load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
472 } catch(std::exception& e) {
473 std::cerr << "Can't load ROM: " << e.what() << std::endl;
474 show_message_ok(NULL, "Error loading ROM", std::string("Error loading ROM:\n\n") +
475 e.what(), wxICON_EXCLAMATION);
476 quit_lua(); //Don't crash.
477 return false;
480 moviefile* mov = NULL;
481 if(movie_file != "")
482 try {
483 mov = new moviefile(movie_file, *rom.rtype);
484 rom.load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
485 } catch(std::exception& e) {
486 std::cerr << "Can't load state: " << e.what() << std::endl;
487 show_message_ok(NULL, "Error loading movie", std::string("Error loading movie:\n\n") +
488 e.what(), wxICON_EXCLAMATION);
489 quit_lua(); //Don't crash.
490 return false;
492 else {
493 mov = new moviefile(rom, c_settings, DEFAULT_RTC_SECOND, DEFAULT_RTC_SUBSECOND);
495 our_rom = rom;
496 mov->start_paused = true;
497 for(auto i : c_lua) {
498 messages << "Trying to run Lua script: " << i << std::endl;
499 lsnes_cmd.invoke("run-lua " + i);
501 boot_emulator(rom, *mov);
502 return true;
505 int lsnes_app::OnExit()
507 if(settings_mode)
508 return 0;
509 //NULL these so no further messages will be sent.
510 auto x = msg_window;
511 msg_window = NULL;
512 main_window = NULL;
513 if(x)
514 x->Destroy();
515 save_configuration();
516 information_dispatch::do_dump_end();
517 quit_lua();
518 movb.release_memory();
519 joystick_driver_signal();
520 joystick_thread_handle->join();
521 platform::quit();
522 cleanup_all_keys();
523 cleanup_keymapper();
524 return 0;
527 namespace
529 struct _graphics_driver drv = {
530 .init = []() -> void {
531 initialize_wx_keyboard();
533 .quit = []() -> void {},
534 .notify_message = []() -> void
536 post_ui_event(UISERV_UPDATE_MESSAGES);
538 .notify_status = []() -> void
540 post_ui_event(UISERV_UPDATE_STATUS);
542 .notify_screen = []() -> void
544 post_ui_event(UISERV_UPDATE_SCREEN);
546 .error_message = [](const std::string& text) -> void {
547 error_message_text = text;
548 post_ui_event(UISERV_ERROR);
550 .fatal_error = []() -> void {
551 //Fun: This can be called from any thread!
552 if(ui_thread == this_thread_id()) {
553 //UI thread.
554 platform::set_modal_pause(true);
555 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"),
556 wxICON_ERROR | wxOK);
557 } else {
558 //Emulation thread panic. Signal the UI thread.
559 post_ui_event(UISERV_PANIC);
560 while(!panic_ack);
563 .name = []() -> const char* { return "wxwidgets graphics plugin"; },
564 .action_updated = []()
566 runuifun([]() -> void { main_window->action_updated(); });
568 .request_rom = [](rom_request& req)
570 rom_request* _req = &req;
571 mutex_class lock;
572 cv_class cv;
573 bool done = false;
574 umutex_class h(lock);
575 runuifun([_req, &lock, &cv, &done]() -> void {
576 try {
577 main_window->request_rom(*_req);
578 } catch(...) {
579 _req->canceled = true;
581 umutex_class h(lock);
582 done = true;
583 cv.notify_all();
585 while(!done)
586 cv.wait(h);
589 struct graphics_driver _drv(drv);
592 void signal_core_change()
594 post_ui_event(UISERV_REFRESH_TITLE);
597 void _runuifun_async(void (*fn)(void*), void* arg)
599 umutex_class h(ui_mutex);
600 ui_queue_entry e;
601 e.fn = fn;
602 e.arg = arg;
603 ui_queue.push_back(e);
604 post_ui_event(UISERV_UIFUN);
608 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
610 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir)
612 wxString _title = towxstring(title);
613 wxString _startdir = towxstring(startdir);
614 std::string filespec;
615 filespec = "All files|*";
616 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), towxstring(filespec), wxFD_OPEN);
617 if(d->ShowModal() == wxID_CANCEL)
618 throw canceled_exception();
619 std::string filename = tostdstring(d->GetPath());
620 d->Destroy();
621 if(filename == "")
622 throw canceled_exception();
623 return filename;
626 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
628 std::string filename = pick_file(parent, title, startdir);
629 //Did we pick a .zip file?
630 if(!regex_match(".*\\.[zZ][iI][pP]", filename))
631 return filename; //Not a ZIP.
632 try {
633 zip::reader zr(filename);
634 std::vector<std::string> files;
635 for(auto i : zr)
636 files.push_back(i);
637 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
638 } catch(canceled_exception& e) {
639 //Throw these forward.
640 throw;
641 } catch(...) {
642 //Ignore error.
644 return filename;
647 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
648 const std::vector<std::string>& choices, unsigned defaultchoice)
650 std::vector<wxString> _choices;
651 for(auto i : choices)
652 _choices.push_back(towxstring(i));
653 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
654 _choices.size(), &_choices[0]);
655 d2->SetSelection(defaultchoice);
656 if(d2->ShowModal() == wxID_CANCEL) {
657 d2->Destroy();
658 throw canceled_exception();
660 std::string out = tostdstring(d2->GetStringSelection());
661 d2->Destroy();
662 return out;
665 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
666 bool multiline)
668 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
669 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
670 if(d2->ShowModal() == wxID_CANCEL) {
671 d2->Destroy();
672 throw canceled_exception();
674 std::string text = tostdstring(d2->GetValue());
675 d2->Destroy();
676 return text;
679 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
681 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
682 d3->ShowModal();
683 d3->Destroy();