Wxwidgets: Allow loading ROMs and movies from commandline
[lsnes.git] / src / platform / wxwidgets / main.cpp
blobcf1275ebfee35fcdae263ffaa91a117916bafd55
1 //Gaah... wx/wx.h (contains something that breaks if included after snes/snes.hpp from bsnes v085.
2 #include <wx/wx.h>
4 #include "lsnes.hpp"
5 #include "core/emucore.hpp"
7 #include "core/command.hpp"
8 #include "core/controller.hpp"
9 #include "core/dispatch.hpp"
10 #include "core/framerate.hpp"
11 #include "lua/lua.hpp"
12 #include "core/mainloop.hpp"
13 #include "core/misc.hpp"
14 #include "core/moviedata.hpp"
15 #include "core/rom.hpp"
16 #include "core/rrdata.hpp"
17 #include "core/settings.hpp"
18 #include "core/window.hpp"
19 #include "library/string.hpp"
20 #include "library/zip.hpp"
22 #include "platform/wxwidgets/platform.hpp"
23 #include "platform/wxwidgets/window_messages.hpp"
24 #include "platform/wxwidgets/window_status.hpp"
25 #include "platform/wxwidgets/window_mainwindow.hpp"
27 #include <cassert>
28 #include <boost/lexical_cast.hpp>
30 #include <wx/wx.h>
31 #include <wx/event.h>
32 #include <wx/control.h>
33 #include <wx/combobox.h>
34 #include <wx/cmdline.h>
36 #define UISERV_RESIZED 9991
37 #define UISERV_UIFUN 9992
38 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
39 #define UISERV_EXIT 9994
40 #define UISERV_UPDATE_STATUS 9995
41 #define UISERV_UPDATE_MESSAGES 9996
42 #define UISERV_UPDATE_SCREEN 9997
43 #define UISERV_PANIC 9998
44 #define UISERV_MODAL 9999
46 wxwin_messages* msg_window;
47 wxwin_mainwindow* main_window;
48 std::string our_rom_name;
50 bool dummy_interface = false;
51 bool wxwidgets_exiting = false;
53 namespace
55 thread_id* ui_thread;
56 volatile bool panic_ack = false;
57 std::string modal_dialog_text;
58 volatile bool modal_dialog_confirm;
59 volatile bool modal_dialog_active;
60 mutex* ui_mutex;
61 condition* ui_condition;
62 thread* joystick_thread_handle;
64 void* joystick_thread(void* _args)
66 joystick_plugin::thread_fn();
69 struct uiserv_event : public wxEvent
71 uiserv_event(int code)
73 SetId(code);
76 wxEvent* Clone() const
78 return new uiserv_event(*this);
82 class ui_services_type : public wxEvtHandler
84 bool ProcessEvent(wxEvent& event);
87 struct ui_queue_entry
89 void(*fn)(void*);
90 void* arg;
93 std::list<ui_queue_entry> ui_queue;
95 bool ui_services_type::ProcessEvent(wxEvent& event)
97 int c = event.GetId();
98 if(c == UISERV_PANIC) {
99 //We need to panic.
100 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR |
101 wxOK);
102 panic_ack = true;
103 } else if(c == UISERV_RESIZED) {
104 if(main_window)
105 main_window->notify_resized();
106 } else if(c == UISERV_MODAL) {
107 std::string text;
108 bool confirm;
110 mutex::holder h(*ui_mutex);
111 text = modal_dialog_text;
112 confirm = modal_dialog_confirm;
114 if(confirm) {
115 int ans = wxMessageBox(towxstring(text), _T("Question"), wxICON_QUESTION | wxOK |
116 wxCANCEL, main_window);
117 confirm = (ans == wxOK);
118 } else {
119 wxMessageBox(towxstring(text), _T("Notification"), wxICON_INFORMATION | wxOK,
120 main_window);
123 mutex::holder h(*ui_mutex);
124 modal_dialog_confirm = confirm;
125 modal_dialog_active = false;
126 ui_condition->signal();
128 } else if(c == UISERV_UPDATE_MESSAGES) {
129 if(msg_window)
130 msg_window->notify_update();
131 } else if(c == UISERV_UPDATE_STATUS) {
132 if(main_window)
133 main_window->notify_update_status();
134 } else if(c == UISERV_UPDATE_SCREEN) {
135 if(main_window)
136 main_window->notify_update();
137 } else if(c == UISERV_EXIT) {
138 if(main_window)
139 main_window->notify_exit();
140 } else if(c == UISERV_UIFUN) {
141 std::list<ui_queue_entry>::iterator i;
142 queue_synchronous_fn_warning = true;
143 back:
145 mutex::holder h(*ui_mutex);
146 if(ui_queue.empty())
147 goto end;
148 i = ui_queue.begin();
150 i->fn(i->arg);
152 mutex::holder h(*ui_mutex);
153 ui_queue.erase(i);
155 goto back;
156 end:
157 queue_synchronous_fn_warning = false;
159 return true;
162 ui_services_type* ui_services;
164 void post_ui_event(int code)
166 uiserv_event uic(code);
167 wxPostEvent(ui_services, uic);
170 void save_configuration()
172 std::string cfg = get_config_path() + "/lsneswxw.rc";
173 std::ofstream cfgfile(cfg.c_str());
174 //Joystick axis.
175 for(auto i : keygroup::get_axis_set()) {
176 keygroup* k = keygroup::lookup_by_name(i);
177 auto p = k->get_parameters();
178 cfgfile << "set-axis " << i << " ";
179 switch(p.ktype) {
180 case keygroup::KT_DISABLED: cfgfile << "disabled"; break;
181 case keygroup::KT_AXIS_PAIR: cfgfile << "axis"; break;
182 case keygroup::KT_AXIS_PAIR_INVERSE: cfgfile << "axis-inverse"; break;
183 case keygroup::KT_PRESSURE_M0: cfgfile << "pressure-0"; break;
184 case keygroup::KT_PRESSURE_MP: cfgfile << "pressure-+"; break;
185 case keygroup::KT_PRESSURE_0M: cfgfile << "pressure0-"; break;
186 case keygroup::KT_PRESSURE_0P: cfgfile << "pressure0+"; break;
187 case keygroup::KT_PRESSURE_PM: cfgfile << "pressure+-"; break;
188 case keygroup::KT_PRESSURE_P0: cfgfile << "pressure+0"; break;
190 cfgfile << " minus=" << p.cal_left << " zero=" << p.cal_center << " plus=" << p.cal_right
191 << " tolerance=" << p.cal_tolerance << std::endl;
193 //Settings.
194 for(auto i : setting::get_settings_set()) {
195 if(!setting::is_set(i))
196 cfgfile << "unset-setting " << i << std::endl;
197 else
198 cfgfile << "set-setting " << i << " " << setting::get(i) << std::endl;
200 for(auto i : setting::get_invalid_values())
201 cfgfile << "set-setting " << i.first << " " << i.second << std::endl;
202 //Aliases.
203 for(auto i : command::get_aliases()) {
204 std::string old_alias_value = command::get_alias_for(i);
205 while(old_alias_value != "") {
206 std::string aliasline;
207 size_t s = old_alias_value.find_first_of("\n");
208 if(s < old_alias_value.length()) {
209 aliasline = old_alias_value.substr(0, s);
210 old_alias_value = old_alias_value.substr(s + 1);
211 } else {
212 aliasline = old_alias_value;
213 old_alias_value = "";
215 cfgfile << "alias-command " << i << " " << aliasline << std::endl;
218 //Keybindings.
219 for(auto i : keymapper::get_bindings()) {
220 std::string i2 = i;
221 size_t s = i2.find_first_of("|");
222 size_t s2 = i2.find_first_of("/");
223 if(s > i2.length() || s2 > s)
224 continue;
225 std::string key = i2.substr(s + 1);
226 std::string mod = i2.substr(0, s2);
227 std::string modspec = i2.substr(s2 + 1, s - s2 - 1);
228 std::string old_command_value = keymapper::get_command_for(i);
229 if(mod != "" || modspec != "")
230 cfgfile << "bind-key " << mod << "/" << modspec << " " << key << " "
231 << old_command_value << std::endl;
232 else
233 cfgfile << "bind-key " << key << " " << old_command_value << std::endl;
235 //Last save.
236 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
237 lsave << last_save;
241 void* eloop_helper(void* x)
243 platform::dummy_event_loop();
244 return NULL;
248 wxString towxstring(const std::string& str) throw(std::bad_alloc)
250 return wxString(str.c_str(), wxConvUTF8);
253 std::string tostdstring(const wxString& str) throw(std::bad_alloc)
255 return std::string(str.mb_str(wxConvUTF8));
258 std::string pick_archive_member(wxWindow* parent, const std::string& filename) throw(std::bad_alloc)
260 //Did we pick a .zip file?
261 std::string f;
262 try {
263 zip_reader zr(filename);
264 std::vector<wxString> files;
265 for(auto i : zr)
266 files.push_back(towxstring(i));
267 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
268 wxT("Select member"), files.size(), &files[0]);
269 if(d2->ShowModal() == wxID_CANCEL) {
270 d2->Destroy();
271 return "";
273 f = filename + "/" + tostdstring(d2->GetStringSelection());
274 d2->Destroy();
275 } catch(...) {
276 //Ignore error.
277 f = filename;
279 return f;
282 void signal_program_exit()
284 post_ui_event(UISERV_EXIT);
287 void signal_resize_needed()
289 post_ui_event(UISERV_RESIZED);
292 void graphics_plugin::init() throw()
294 initialize_wx_keyboard();
297 void graphics_plugin::quit() throw()
301 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
302 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
303 wxCMD_LINE_PARAM_MULTIPLE },
304 { wxCMD_LINE_NONE }
307 class lsnes_app : public wxApp
309 public:
310 lsnes_app();
311 virtual bool OnInit();
312 virtual int OnExit();
313 virtual void OnInitCmdLine(wxCmdLineParser& parser);
314 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
315 private:
316 bool settings_mode;
317 std::string c_rom;
318 std::string c_file;
321 IMPLEMENT_APP(lsnes_app)
323 lsnes_app::lsnes_app()
325 settings_mode = false;
328 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
330 parser.SetDesc(dummy_descriptor_table);
331 parser.SetSwitchChars(wxT(""));
334 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
336 std::vector<std::string> cmdline;
337 for(size_t i = 0; i< parser.GetParamCount(); i++)
338 cmdline.push_back(tostdstring(parser.GetParam(i)));
339 for(auto i: cmdline) {
340 regex_results r;
341 if(i == "--settings")
342 settings_mode = true;
343 if(r = regex("--rom=(.+)", i))
344 c_rom = r[1];
345 if(r = regex("--load=(.+)", i))
346 c_file = r[1];
351 bool lsnes_app::OnInit()
353 wxApp::OnInit();
355 reached_main();
356 set_random_seed();
357 bring_app_foreground();
359 ui_services = new ui_services_type();
360 ui_mutex = &mutex::aquire();
361 ui_condition = &condition::aquire(*ui_mutex);
363 bsnes_core_version = get_core_identifier();
364 ui_thread = &thread_id::me();
365 platform::init();
367 messages << "BSNES version: " << bsnes_core_version << std::endl;
368 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
370 controls.set_port(0, porttype_info::port_default(0), false);
371 controls.set_port(1, porttype_info::port_default(1), false);
373 std::string cfgpath = get_config_path();
374 messages << "Saving per-user data to: " << get_config_path() << std::endl;
375 messages << "--- Running lsnesrc --- " << std::endl;
376 setting::set_storage_mode(true);
377 command::invokeC("run-script " + cfgpath + "/lsneswxw.rc");
378 setting::set_storage_mode(false);
379 messages << "--- End running lsnesrc --- " << std::endl;
381 if(settings_mode) {
382 //We got to boot this up quite a bit to get the joystick driver working.
383 //In practicular, we need joystick thread and emulator thread in pause.
384 joystick_thread_handle = &thread::create(joystick_thread, NULL);
385 thread* dummy_loop = &thread::create(eloop_helper, NULL);
386 wxsetingsdialog_display(NULL, false);
387 platform::exit_dummy_event_loop();
388 joystick_plugin::signal();
389 joystick_thread_handle->join();
390 dummy_loop->join();
391 save_configuration();
392 return false;
394 init_lua();
396 joystick_thread_handle = &thread::create(joystick_thread, NULL);
398 msg_window = new wxwin_messages();
399 msg_window->Show();
401 do_basic_core_init();
402 loaded_rom* rom = NULL;
403 if(c_rom != "")
404 try {
405 moviefile mov;
406 rom = new loaded_rom(c_rom);
407 rom->load(mov.movie_rtc_second, mov.movie_rtc_subsecond);
408 } catch(std::exception& e) {
409 std::cerr << "Can't load ROM: " << e.what() << std::endl;
410 return false;
412 else
413 rom = new loaded_rom;
414 moviefile* mov = NULL;
415 if(c_file != "")
416 try {
417 mov = new moviefile(c_file);
418 if(c_rom != "")
419 rom->load(mov->movie_rtc_second, mov->movie_rtc_subsecond);
420 } catch(std::exception& e) {
421 std::cerr << "Can't load state: " << e.what() << std::endl;
422 return false;
424 else {
425 mov = new moviefile;
426 mov->port1 = &porttype_info::port_default(0);
427 mov->port2 = &porttype_info::port_default(1);
428 mov->input.clear(*mov->port1, *mov->port2);
429 if(c_rom != "") {
430 //Initialize the remainder.
431 mov->coreversion = bsnes_core_version;
432 mov->projectid = get_random_hexstring(40);
433 mov->rerecords = "0";
434 for(size_t i = 0; i < sizeof(rom->romimg)/sizeof(rom->romimg[0]); i++) {
435 mov->romimg_sha256[i] = rom->romimg[i].sha256;
436 mov->romxml_sha256[i] = rom->romxml[i].sha256;
438 mov->gametype = &rom->rtype->combine_region(*rom->region);
441 mov->start_paused = true;
442 boot_emulator(*rom, *mov);
444 return true;
447 int lsnes_app::OnExit()
449 if(settings_mode)
450 return 0;
451 //NULL these so no further messages will be sent.
452 auto x = msg_window;
453 auto y = main_window;
454 msg_window = NULL;
455 main_window = NULL;
456 save_configuration();
457 information_dispatch::do_dump_end();
458 rrdata::close();
459 joystick_plugin::signal();
460 joystick_thread_handle->join();
461 platform::quit();
462 return 0;
465 void graphics_plugin::notify_message() throw()
467 post_ui_event(UISERV_UPDATE_MESSAGES);
470 void graphics_plugin::notify_status() throw()
472 post_ui_event(UISERV_UPDATE_STATUS);
475 void graphics_plugin::notify_screen() throw()
477 post_ui_event(UISERV_UPDATE_SCREEN);
480 bool graphics_plugin::modal_message(const std::string& text, bool confirm) throw()
482 mutex::holder h(*ui_mutex);
483 modal_dialog_active = true;
484 modal_dialog_confirm = confirm;
485 modal_dialog_text = text;
486 post_ui_event(UISERV_MODAL);
487 while(modal_dialog_active)
488 ui_condition->wait(10000);
489 return modal_dialog_confirm;
492 void graphics_plugin::fatal_error() throw()
494 //Fun: This can be called from any thread!
495 if(ui_thread->is_me()) {
496 //UI thread.
497 platform::set_modal_pause(true);
498 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR | wxOK);
499 } else {
500 //Emulation thread panic. Signal the UI thread.
501 post_ui_event(UISERV_PANIC);
502 while(!panic_ack);
506 void _runuifun_async(void (*fn)(void*), void* arg)
508 mutex::holder h(*ui_mutex);
509 ui_queue_entry e;
510 e.fn = fn;
511 e.arg = arg;
512 ui_queue.push_back(e);
513 auto i = ui_queue.insert(ui_queue.end(), e);
514 post_ui_event(UISERV_UIFUN);
518 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
520 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir, bool forsave)
522 wxString _title = towxstring(title);
523 wxString _startdir = towxstring(startdir);
524 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), wxT("All files|*"), forsave ?
525 wxFD_SAVE : wxFD_OPEN);
526 if(d->ShowModal() == wxID_CANCEL)
527 throw canceled_exception();
528 std::string filename = tostdstring(d->GetPath());
529 d->Destroy();
530 if(filename == "")
531 throw canceled_exception();
532 return filename;
535 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
537 std::string filename = pick_file(parent, title, startdir, false);
538 //Did we pick a .zip file?
539 try {
540 zip_reader zr(filename);
541 std::vector<std::string> files;
542 for(auto i : zr)
543 files.push_back(i);
544 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
545 } catch(canceled_exception& e) {
546 //Throw these forward.
547 throw;
548 } catch(...) {
549 //Ignore error.
551 return filename;
554 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
555 const std::vector<std::string>& choices)
557 std::vector<wxString> _choices;
558 for(auto i : choices)
559 _choices.push_back(towxstring(i));
560 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
561 _choices.size(), &_choices[0]);
562 if(d2->ShowModal() == wxID_CANCEL) {
563 d2->Destroy();
564 throw canceled_exception();
566 std::string out = tostdstring(d2->GetStringSelection());
567 d2->Destroy();
568 return out;
571 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
572 bool multiline)
574 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
575 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
576 if(d2->ShowModal() == wxID_CANCEL) {
577 d2->Destroy();
578 throw canceled_exception();
580 std::string text = tostdstring(d2->GetValue());
581 d2->Destroy();
582 return text;
585 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
587 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
588 d3->ShowModal();
589 d3->Destroy();
593 const char* graphics_plugin::name = "wxwidgets graphics plugin";