Remove do_basic_core_init (do equivalent tasks on demand)
[lsnes.git] / src / platform / wxwidgets / main.cpp
blobd7e43a9124c57772af6bb51b58136b15adb1ae95
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>
35 #include <iostream>
37 #define UISERV_RESIZED 9991
38 #define UISERV_UIFUN 9992
39 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
40 #define UISERV_EXIT 9994
41 #define UISERV_UPDATE_STATUS 9995
42 #define UISERV_UPDATE_MESSAGES 9996
43 #define UISERV_UPDATE_SCREEN 9997
44 #define UISERV_PANIC 9998
45 #define UISERV_MODAL 9999
47 wxwin_messages* msg_window;
48 wxwin_mainwindow* main_window;
49 std::string our_rom_name;
51 bool dummy_interface = false;
52 bool wxwidgets_exiting = false;
54 namespace
56 thread_id* ui_thread;
57 volatile bool panic_ack = false;
58 std::string modal_dialog_text;
59 volatile bool modal_dialog_confirm;
60 volatile bool modal_dialog_active;
61 mutex* ui_mutex;
62 condition* ui_condition;
63 thread* joystick_thread_handle;
65 void* joystick_thread(void* _args)
67 joystick_plugin::thread_fn();
70 struct uiserv_event : public wxEvent
72 uiserv_event(int code)
74 SetId(code);
77 wxEvent* Clone() const
79 return new uiserv_event(*this);
83 class ui_services_type : public wxEvtHandler
85 bool ProcessEvent(wxEvent& event);
88 struct ui_queue_entry
90 void(*fn)(void*);
91 void* arg;
94 std::list<ui_queue_entry> ui_queue;
96 bool ui_services_type::ProcessEvent(wxEvent& event)
98 int c = event.GetId();
99 if(c == UISERV_PANIC) {
100 //We need to panic.
101 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR |
102 wxOK);
103 panic_ack = true;
104 } else if(c == UISERV_RESIZED) {
105 if(main_window)
106 main_window->notify_resized();
107 } else if(c == UISERV_MODAL) {
108 std::string text;
109 bool confirm;
111 mutex::holder h(*ui_mutex);
112 text = modal_dialog_text;
113 confirm = modal_dialog_confirm;
115 if(confirm) {
116 int ans = wxMessageBox(towxstring(text), _T("Question"), wxICON_QUESTION | wxOK |
117 wxCANCEL, main_window);
118 confirm = (ans == wxOK);
119 } else {
120 wxMessageBox(towxstring(text), _T("Notification"), wxICON_INFORMATION | wxOK,
121 main_window);
124 mutex::holder h(*ui_mutex);
125 modal_dialog_confirm = confirm;
126 modal_dialog_active = false;
127 ui_condition->signal();
129 } else if(c == UISERV_UPDATE_MESSAGES) {
130 if(msg_window)
131 msg_window->notify_update();
132 } else if(c == UISERV_UPDATE_STATUS) {
133 if(main_window)
134 main_window->notify_update_status();
135 } else if(c == UISERV_UPDATE_SCREEN) {
136 if(main_window)
137 main_window->notify_update();
138 } else if(c == UISERV_EXIT) {
139 if(main_window)
140 main_window->notify_exit();
141 } else if(c == UISERV_UIFUN) {
142 std::list<ui_queue_entry>::iterator i;
143 queue_synchronous_fn_warning = true;
144 back:
146 mutex::holder h(*ui_mutex);
147 if(ui_queue.empty())
148 goto end;
149 i = ui_queue.begin();
151 i->fn(i->arg);
153 mutex::holder h(*ui_mutex);
154 ui_queue.erase(i);
156 goto back;
157 end:
158 queue_synchronous_fn_warning = false;
160 return true;
163 ui_services_type* ui_services;
165 void post_ui_event(int code)
167 uiserv_event uic(code);
168 wxPostEvent(ui_services, uic);
171 std::map<std::string, std::string> saved_buttons;
173 void handle_config_line(std::string line)
175 regex_results r;
176 if(r = regex("AXIS[ \t]+([^ \t]+)[ \t]+(-?[0-9])[ \t]+(-?[0-9])[ \t]+(-?[0-9])[ \t]+"
177 "(-?[0-9]+)[ \t]+(-?[0-9]+)[ \t]+(-?[0-9]+)[ \t]+(0?.[0-9]+)[ \t]*", line)) {
178 keyboard_axis_calibration c;
179 c.mode = parse_value<int>(r[2]);
180 if(c.mode < -1 || c.mode > 1) {
181 messages << "Illegal axis mode " << c.mode << std::endl;
182 return;
184 c.esign_a = parse_value<int>(r[3]);
185 c.esign_b = parse_value<int>(r[4]);
186 if(c.esign_a < -1 || c.esign_a > 1 || c.esign_b < -1 || c.esign_b > 1 ||
187 c.esign_a == c.esign_b || (c.mode == 1 && (c.esign_a == 0 || c.esign_b == 0))) {
188 messages << "Illegal axis endings " << c.esign_a << "/" << c.esign_b << std::endl;
189 return;
191 c.left = parse_value<int32_t>(r[5]);
192 c.center = parse_value<int32_t>(r[6]);
193 c.right = parse_value<int32_t>(r[7]);
194 c.nullwidth = parse_value<double>(r[8]);
195 keyboard_key* _k = lsnes_kbd.try_lookup_key(r[1]);
196 keyboard_key_axis* k = NULL;
197 if(_k)
198 k = _k->cast_axis();
199 if(!k)
200 return;
201 k->set_calibration(c);
202 messages << "Calibration of " << r[1] << " changed: mode=" << calibration_to_mode(c)
203 << " limits=" << c.left << "(" << c.center << ")" << c.right
204 << " null=" << c.nullwidth << std::endl;
205 } else if(r = regex("UNSET[ \t]+([^ \t]+)[ \t]*", line)) {
206 try {
207 lsnes_set.blank(r[1]);
208 messages << "Setting " << r[1] << " unset" << std::endl;
209 } catch(std::exception& e) {
210 messages << "Can't unset " << r[1] << ": " << e.what() << std::endl;
212 } else if(r = regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
213 try {
214 lsnes_set.set(r[1], r[2]);
215 messages << "Setting " << r[1] << " set to " << r[2] << std::endl;
216 } catch(std::exception& e) {
217 messages << "Can't set " << r[1] << ": " << e.what() << std::endl;
219 } else if(r = regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line)) {
220 if(!lsnes_cmd.valid_alias_name(r[1])) {
221 messages << "Illegal alias name " << r[1] << std::endl;
222 return;
224 std::string tmp = lsnes_cmd.get_alias_for(r[1]);
225 tmp = tmp + r[2] + "\n";
226 lsnes_cmd.set_alias_for(r[1], tmp);
227 messages << r[1] << " aliased to " << r[2] << std::endl;
228 } else if(r = regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line)) {
229 lsnes_mapper.bind(r[1], r[2], r[3], r[4]);
230 if(r[1] != "" || r[2] != "")
231 messages << r[1] << "/" << r[2] << " ";
232 messages << r[3] << " bound to '" << r[4] << "'" << std::endl;
233 } else if(r = regex("BUTTON[ \t]+([^ ]+)[ \t](.*)", line)) {
234 controller_key* ckey = lsnes_mapper.get_controllerkey(r[2]);
235 if(ckey) {
236 ckey->set(r[1]);
237 messages << r[1] << " bound (button) to " << r[2] << std::endl;
238 } else
239 saved_buttons[r[2]] = r[1];
240 } else
241 messages << "Unrecognized directive: " << line << std::endl;
244 void load_configuration()
246 std::string cfg = get_config_path() + "/lsneswxw.cfg";
247 std::ifstream cfgfile(cfg.c_str());
248 std::string line;
249 while(std::getline(cfgfile, line))
250 try {
251 handle_config_line(line);
252 } catch(std::exception& e) {
253 messages << "Error processing line: " << e.what() << std::endl;
257 void save_configuration()
259 std::string cfg = get_config_path() + "/lsneswxw.cfg";
260 std::ofstream cfgfile(cfg.c_str());
261 //Joystick axis.
262 for(auto i : lsnes_kbd.all_keys()) {
263 keyboard_key_axis* j = i->cast_axis();
264 if(!j)
265 continue;
266 auto p = j->get_calibration();
267 cfgfile << "AXIS " << i->get_name() << " " << p.mode << " " << p.esign_a << " " << p.esign_b
268 << " " << p.left << " " << p.center << " " << p.right << " " << p.nullwidth
269 << std::endl;
271 //Settings.
272 for(auto i : lsnes_set.get_settings_set()) {
273 if(!lsnes_set.is_set(i))
274 cfgfile << "UNSET " << i << std::endl;
275 else
276 cfgfile << "SET " << i << " " << lsnes_set.get(i) << std::endl;
278 for(auto i : lsnes_set.get_invalid_values())
279 cfgfile << "SET " << i.first << " " << i.second << std::endl;
280 //Aliases.
281 for(auto i : lsnes_cmd.get_aliases()) {
282 std::string old_alias_value = lsnes_cmd.get_alias_for(i);
283 while(old_alias_value != "") {
284 std::string aliasline;
285 size_t s = old_alias_value.find_first_of("\n");
286 if(s < old_alias_value.length()) {
287 aliasline = old_alias_value.substr(0, s);
288 old_alias_value = old_alias_value.substr(s + 1);
289 } else {
290 aliasline = old_alias_value;
291 old_alias_value = "";
293 cfgfile << "ALIAS " << i << " " << aliasline << std::endl;
296 //Keybindings.
297 for(auto i : lsnes_mapper.get_bindings())
298 cfgfile << "BIND " << std::string(i) << " " << lsnes_mapper.get(i) << std::endl;
299 //Buttons.
300 for(auto i : lsnes_mapper.get_controller_keys()) {
301 std::string b = i->get_string();
302 if(b != "")
303 cfgfile << "BUTTON " << b << " " << i->get_command() << std::endl;
305 for(auto i : saved_buttons)
306 cfgfile << "BUTTON " << i.second << " " << i.first << std::endl;
307 //Last save.
308 std::ofstream lsave(get_config_path() + "/" + our_rom_name + ".ls");
309 lsave << last_save;
313 void* eloop_helper(void* x)
315 platform::dummy_event_loop();
316 return NULL;
320 wxString towxstring(const std::string& str) throw(std::bad_alloc)
322 return wxString(str.c_str(), wxConvUTF8);
325 std::string tostdstring(const wxString& str) throw(std::bad_alloc)
327 return std::string(str.mb_str(wxConvUTF8));
330 std::string pick_archive_member(wxWindow* parent, const std::string& filename) throw(std::bad_alloc)
332 //Did we pick a .zip file?
333 std::string f;
334 try {
335 zip_reader zr(filename);
336 std::vector<wxString> files;
337 for(auto i : zr)
338 files.push_back(towxstring(i));
339 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, wxT("Select file within .zip"),
340 wxT("Select member"), files.size(), &files[0]);
341 if(d2->ShowModal() == wxID_CANCEL) {
342 d2->Destroy();
343 return "";
345 f = filename + "/" + tostdstring(d2->GetStringSelection());
346 d2->Destroy();
347 } catch(...) {
348 //Ignore error.
349 f = filename;
351 return f;
354 void signal_program_exit()
356 post_ui_event(UISERV_EXIT);
359 void signal_resize_needed()
361 post_ui_event(UISERV_RESIZED);
364 void graphics_plugin::init() throw()
366 initialize_wx_keyboard();
369 void graphics_plugin::quit() throw()
373 static const wxCmdLineEntryDesc dummy_descriptor_table[] = {
374 { wxCMD_LINE_PARAM, NULL, NULL, NULL, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL |
375 wxCMD_LINE_PARAM_MULTIPLE },
376 { wxCMD_LINE_NONE }
379 class lsnes_app : public wxApp
381 public:
382 lsnes_app();
383 virtual bool OnInit();
384 virtual int OnExit();
385 virtual void OnInitCmdLine(wxCmdLineParser& parser);
386 virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
387 private:
388 bool settings_mode;
389 std::string c_rom;
390 std::string c_file;
391 std::map<std::string, std::string> c_settings;
394 IMPLEMENT_APP(lsnes_app)
396 lsnes_app::lsnes_app()
398 settings_mode = false;
401 void lsnes_app::OnInitCmdLine(wxCmdLineParser& parser)
403 parser.SetDesc(dummy_descriptor_table);
404 parser.SetSwitchChars(wxT(""));
407 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser& parser)
409 std::vector<std::string> cmdline;
410 for(size_t i = 0; i< parser.GetParamCount(); i++)
411 cmdline.push_back(tostdstring(parser.GetParam(i)));
412 for(auto i: cmdline) {
413 regex_results r;
414 if(i == "--settings")
415 settings_mode = true;
416 if(r = regex("--rom=(.+)", i))
417 c_rom = r[1];
418 if(r = regex("--load=(.+)", i))
419 c_file = r[1];
420 if(r = regex("--set=([^=]+)=(.+)", i))
421 c_settings[r[1]] = r[2];
426 bool lsnes_app::OnInit()
428 wxApp::OnInit();
430 reached_main();
431 set_random_seed();
432 bring_app_foreground();
434 ui_services = new ui_services_type();
435 ui_mutex = &mutex::aquire();
436 ui_condition = &condition::aquire(*ui_mutex);
438 bsnes_core_version = emulator_core->get_core_identifier();
439 ui_thread = &thread_id::me();
440 platform::init();
442 messages << "BSNES version: " << bsnes_core_version << std::endl;
443 messages << "lsnes version: lsnes rr" << lsnes_version << std::endl;
445 loaded_rom dummy_rom;
446 std::map<std::string, std::string> settings;
447 auto ctrldata = dummy_rom.rtype->controllerconfig(settings);
448 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex);
450 controls.set_ports(ports);
452 std::string cfgpath = get_config_path();
453 messages << "Saving per-user data to: " << get_config_path() << std::endl;
454 messages << "--- Loading configuration --- " << std::endl;
455 lsnes_set.set_storage_mode(true);
456 load_configuration();
457 lsnes_set.set_storage_mode(false);
458 messages << "--- End running lsnesrc --- " << std::endl;
460 if(settings_mode) {
461 //We got to boot this up quite a bit to get the joystick driver working.
462 //In practicular, we need joystick thread and emulator thread in pause.
463 joystick_thread_handle = &thread::create(joystick_thread, NULL);
464 thread* dummy_loop = &thread::create(eloop_helper, NULL);
465 wxsetingsdialog_display(NULL, false);
466 platform::exit_dummy_event_loop();
467 joystick_plugin::signal();
468 joystick_thread_handle->join();
469 dummy_loop->join();
470 save_configuration();
471 return false;
473 init_lua();
475 joystick_thread_handle = &thread::create(joystick_thread, NULL);
477 msg_window = new wxwin_messages();
478 msg_window->Show();
480 loaded_rom* rom = NULL;
481 if(c_rom != "")
482 try {
483 moviefile mov;
484 rom = new loaded_rom(c_rom);
485 rom->load(c_settings, mov.movie_rtc_second, mov.movie_rtc_subsecond);
486 } catch(std::exception& e) {
487 std::cerr << "Can't load ROM: " << e.what() << std::endl;
488 return false;
490 else
491 rom = new loaded_rom;
492 moviefile* mov = NULL;
493 if(c_file != "")
494 try {
495 mov = new moviefile(c_file);
496 if(c_rom != "")
497 rom->load(mov->settings, mov->movie_rtc_second, mov->movie_rtc_subsecond);
498 } catch(std::exception& e) {
499 std::cerr << "Can't load state: " << e.what() << std::endl;
500 return false;
502 else {
503 mov = new moviefile;
504 mov->settings = c_settings;
505 auto ctrldata = rom->rtype->controllerconfig(mov->settings);
506 port_type_set& ports = port_type_set::make(ctrldata.ports, ctrldata.portindex);
507 mov->input.clear(ports);
508 if(c_rom != "") {
509 //Initialize the remainder.
510 mov->coreversion = bsnes_core_version;
511 mov->projectid = get_random_hexstring(40);
512 mov->rerecords = "0";
513 for(size_t i = 0; i < sizeof(rom->romimg)/sizeof(rom->romimg[0]); i++) {
514 mov->romimg_sha256[i] = rom->romimg[i].sha_256;
515 mov->romxml_sha256[i] = rom->romxml[i].sha_256;
518 mov->gametype = &rom->rtype->combine_region(*rom->region);
520 mov->start_paused = true;
521 boot_emulator(*rom, *mov);
523 return true;
526 int lsnes_app::OnExit()
528 if(settings_mode)
529 return 0;
530 //NULL these so no further messages will be sent.
531 auto x = msg_window;
532 auto y = main_window;
533 msg_window = NULL;
534 main_window = NULL;
535 if(x)
536 x->Destroy();
537 save_configuration();
538 information_dispatch::do_dump_end();
539 rrdata::close();
540 quit_lua();
541 joystick_plugin::signal();
542 joystick_thread_handle->join();
543 platform::quit();
544 return 0;
547 void graphics_plugin::notify_message() throw()
549 post_ui_event(UISERV_UPDATE_MESSAGES);
552 void graphics_plugin::notify_status() throw()
554 post_ui_event(UISERV_UPDATE_STATUS);
557 void graphics_plugin::notify_screen() throw()
559 post_ui_event(UISERV_UPDATE_SCREEN);
562 bool graphics_plugin::modal_message(const std::string& text, bool confirm) throw()
564 mutex::holder h(*ui_mutex);
565 modal_dialog_active = true;
566 modal_dialog_confirm = confirm;
567 modal_dialog_text = text;
568 post_ui_event(UISERV_MODAL);
569 while(modal_dialog_active)
570 ui_condition->wait(10000);
571 return modal_dialog_confirm;
574 void graphics_plugin::fatal_error() throw()
576 //Fun: This can be called from any thread!
577 if(ui_thread->is_me()) {
578 //UI thread.
579 platform::set_modal_pause(true);
580 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR | wxOK);
581 } else {
582 //Emulation thread panic. Signal the UI thread.
583 post_ui_event(UISERV_PANIC);
584 while(!panic_ack);
588 void _runuifun_async(void (*fn)(void*), void* arg)
590 mutex::holder h(*ui_mutex);
591 ui_queue_entry e;
592 e.fn = fn;
593 e.arg = arg;
594 ui_queue.push_back(e);
595 post_ui_event(UISERV_UIFUN);
599 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
601 std::string pick_file(wxWindow* parent, const std::string& title, const std::string& startdir, bool forsave)
603 wxString _title = towxstring(title);
604 wxString _startdir = towxstring(startdir);
605 wxFileDialog* d = new wxFileDialog(parent, _title, _startdir, wxT(""), wxT("All files|*"), forsave ?
606 wxFD_SAVE : wxFD_OPEN);
607 if(d->ShowModal() == wxID_CANCEL)
608 throw canceled_exception();
609 std::string filename = tostdstring(d->GetPath());
610 d->Destroy();
611 if(filename == "")
612 throw canceled_exception();
613 return filename;
616 std::string pick_file_member(wxWindow* parent, const std::string& title, const std::string& startdir)
618 std::string filename = pick_file(parent, title, startdir, false);
619 //Did we pick a .zip file?
620 try {
621 zip_reader zr(filename);
622 std::vector<std::string> files;
623 for(auto i : zr)
624 files.push_back(i);
625 filename = filename + "/" + pick_among(parent, "Select member", "Select file within .zip", files);
626 } catch(canceled_exception& e) {
627 //Throw these forward.
628 throw;
629 } catch(...) {
630 //Ignore error.
632 return filename;
635 std::string pick_among(wxWindow* parent, const std::string& title, const std::string& prompt,
636 const std::vector<std::string>& choices)
638 std::vector<wxString> _choices;
639 for(auto i : choices)
640 _choices.push_back(towxstring(i));
641 wxSingleChoiceDialog* d2 = new wxSingleChoiceDialog(parent, towxstring(prompt), towxstring(title),
642 _choices.size(), &_choices[0]);
643 if(d2->ShowModal() == wxID_CANCEL) {
644 d2->Destroy();
645 throw canceled_exception();
647 std::string out = tostdstring(d2->GetStringSelection());
648 d2->Destroy();
649 return out;
652 std::string pick_text(wxWindow* parent, const std::string& title, const std::string& prompt, const std::string& dflt,
653 bool multiline)
655 wxTextEntryDialog* d2 = new wxTextEntryDialog(parent, towxstring(prompt), towxstring(title), towxstring(dflt),
656 wxOK | wxCANCEL | wxCENTRE | (multiline ? wxTE_MULTILINE : 0));
657 if(d2->ShowModal() == wxID_CANCEL) {
658 d2->Destroy();
659 throw canceled_exception();
661 std::string text = tostdstring(d2->GetValue());
662 d2->Destroy();
663 return text;
666 void show_message_ok(wxWindow* parent, const std::string& title, const std::string& text, int icon)
668 wxMessageDialog* d3 = new wxMessageDialog(parent, towxstring(text), towxstring(title), wxOK | icon);
669 d3->ShowModal();
670 d3->Destroy();
674 const char* graphics_plugin::name = "wxwidgets graphics plugin";