Fix scaling-related crashes
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob24973a24e049b52ec5d9e639edf6acf20c7d4118
1 #include "lsnes.hpp"
3 #include <wx/dnd.h>
4 #include "platform/wxwidgets/menu_dump.hpp"
5 #include "platform/wxwidgets/menu_upload.hpp"
6 #include "platform/wxwidgets/platform.hpp"
7 #include "platform/wxwidgets/loadsave.hpp"
8 #include "platform/wxwidgets/window_mainwindow.hpp"
9 #include "platform/wxwidgets/window_messages.hpp"
10 #include "platform/wxwidgets/window_status.hpp"
11 #include "platform/wxwidgets/window-romload.hpp"
12 #include "platform/wxwidgets/settings-common.hpp"
13 #include "platform/wxwidgets/menu_tracelog.hpp"
14 #include "platform/wxwidgets/menu_branches.hpp"
15 #include "platform/wxwidgets/menu_projects.hpp"
17 #include "cmdhelp/framebuffer.hpp"
18 #include "cmdhelp/loadsave.hpp"
19 #include "cmdhelp/lua.hpp"
20 #include "cmdhelp/subtitles.hpp"
21 #include "core/audioapi.hpp"
22 #include "core/audioapi-driver.hpp"
23 #include "core/command.hpp"
24 #include "core/controller.hpp"
25 #include "core/controllerframe.hpp"
26 #include "core/dispatch.hpp"
27 #include "core/emustatus.hpp"
28 #include "core/framebuffer.hpp"
29 #include "core/framerate.hpp"
30 #include "core/instance.hpp"
31 #include "core/keymapper.hpp"
32 #include "core/ui-services.hpp"
33 #include "interface/romtype.hpp"
34 #include "core/loadlib.hpp"
35 #include "lua/lua.hpp"
36 #include "core/mainloop.hpp"
37 #include "core/memorywatch.hpp"
38 #include "core/messages.hpp"
39 #include "core/misc.hpp"
40 #include "core/moviedata.hpp"
41 #include "core/project.hpp"
42 #include "core/rom.hpp"
43 #include "core/settings.hpp"
44 #include "core/window.hpp"
45 #include "library/directory.hpp"
46 #include "library/minmax.hpp"
47 #include "library/string.hpp"
48 #include "library/zip.hpp"
49 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
50 #define FUCKED_SYSTEM
51 #endif
53 #include <cmath>
54 #include <vector>
55 #include <string>
58 extern "C"
60 #ifndef UINT64_C
61 #define UINT64_C(val) val##ULL
62 #endif
63 #include <libswscale/swscale.h>
66 enum
68 wxID_PAUSE = wxID_HIGHEST + 1,
69 wxID_FRAMEADVANCE,
70 wxID_SUBFRAMEADVANCE,
71 wxID_NEXTPOLL,
72 wxID_AUDIO_ENABLED,
73 wxID_SAVE_STATE,
74 wxID_SAVE_MOVIE,
75 wxID_SAVE_SUBTITLES,
76 wxID_LOAD_STATE,
77 wxID_LOAD_MOVIE,
78 wxID_RUN_SCRIPT,
79 wxID_RUN_LUA,
80 wxID_RESET_LUA,
81 wxID_EVAL_LUA,
82 wxID_SAVE_SCREENSHOT,
83 wxID_READONLY_MODE,
84 wxID_EDIT_AUTHORS,
85 wxID_AUTOHOLD,
86 wxID_EDIT_MEMORYWATCH,
87 wxID_SAVE_MEMORYWATCH,
88 wxID_LOAD_MEMORYWATCH,
89 wxID_EDIT_SUBTITLES,
90 wxID_EDIT_VSUBTITLES,
91 wxID_DUMP_FIRST,
92 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
93 wxID_REWIND_MOVIE,
94 wxID_MEMORY_SEARCH,
95 wxID_CANCEL_SAVES,
96 wxID_SHOW_STATUS,
97 wxID_SET_SPEED,
98 wxID_SPEED_5,
99 wxID_SPEED_10,
100 wxID_SPEED_17,
101 wxID_SPEED_20,
102 wxID_SPEED_25,
103 wxID_SPEED_33,
104 wxID_SPEED_50,
105 wxID_SPEED_100,
106 wxID_SPEED_150,
107 wxID_SPEED_200,
108 wxID_SPEED_300,
109 wxID_SPEED_500,
110 wxID_SPEED_1000,
111 wxID_SPEED_TURBO,
112 wxID_LOAD_LIBRARY,
113 wxID_RELOAD_ROM_IMAGE,
114 wxID_LOAD_ROM_IMAGE_FIRST,
115 wxID_LOAD_ROM_IMAGE_LAST = wxID_LOAD_ROM_IMAGE_FIRST + 1023,
116 wxID_NEW_MOVIE,
117 wxID_SHOW_MESSAGES,
118 wxID_DEDICATED_MEMORY_WATCH,
119 wxID_RMOVIE_FIRST,
120 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
121 wxID_RROM_FIRST,
122 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
123 wxID_CONFLICTRESOLUTION,
124 wxID_VUDISPLAY,
125 wxID_MOVIE_EDIT,
126 wxID_TASINPUT,
127 wxID_NEW_PROJECT,
128 wxID_CLOSE_PROJECT,
129 wxID_CLOSE_ROM,
130 wxID_EDIT_MACROS,
131 wxID_ENTER_FULLSCREEN,
132 wxID_ACTIONS_FIRST,
133 wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256,
134 wxID_SETTINGS_FIRST,
135 wxID_SETTINGS_LAST = wxID_SETTINGS_FIRST + 256,
136 wxID_HEXEDITOR,
137 wxID_MULTITRACK,
138 wxID_CHDIR,
139 wxID_RLUA_FIRST,
140 wxID_RLUA_LAST = wxID_RLUA_FIRST + 16,
141 wxID_UPLOAD_FIRST,
142 wxID_UPLOAD_LAST = wxID_UPLOAD_FIRST + 256,
143 wxID_DOWNLOAD,
144 wxID_TRACELOG_FIRST,
145 wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256,
146 wxID_PLUGIN_MANAGER,
147 wxID_BRANCH_FIRST,
148 wxID_BRANCH_LAST = wxID_BRANCH_FIRST + 10240,
149 wxID_PROJECT_FIRST,
150 wxID_PROJECT_LAST = wxID_PROJECT_FIRST + 17,
151 wxID_DISASSEMBLER,
155 double video_scale_factor = 1.0;
156 int scaling_flags = SWS_POINT;
157 bool arcorrect_enabled = false;
158 bool hflip_enabled = false;
159 bool vflip_enabled = false;
160 bool rotate_enabled = false;
162 namespace
164 std::string last_volume = "0dB";
165 std::string last_volume_record = "0dB";
166 std::string last_volume_voice = "0dB";
167 unsigned char* screen_buffer;
168 struct SwsContext* sws_ctx;
169 uint32_t* rotate_buffer;
170 uint32_t old_width;
171 uint32_t old_height;
172 int old_flags = SWS_POINT;
173 bool old_hflip = false;
174 bool old_vflip = false;
175 bool old_rotate = false;
176 bool main_window_dirty;
177 bool is_fs = false;
178 bool hashing_in_progress = false;
179 uint64_t hashing_left = 0;
180 uint64_t hashing_total = 0;
181 int64_t last_update = 0;
182 threads::thread* emulation_thread;
183 bool status_updated = false;
185 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> background_audio(*lsnes_instance.settings,
186 "background-audio", "GUIā€£Enable background audio", true);
188 class _status_timer : public wxTimer
190 public:
191 _status_timer()
193 Start(50);
195 void Notify()
197 if(status_updated) {
198 status_updated = false;
199 if(main_window) main_window->update_statusbar();
204 void cleanup_dead_download_timers();
205 class _focus_timer : public wxTimer
207 public:
208 _focus_timer()
210 was_focused = (wxWindow::FindFocus() != NULL);
211 was_enabled = platform::is_sound_enabled();
212 Start(500);
214 void Notify()
216 CHECK_UI_THREAD;
217 bool is_focused = (wxWindow::FindFocus() != NULL);
218 if(is_focused && !was_focused) {
219 //Gained focus.
220 if(!background_audio)
221 platform::sound_enable(was_enabled);
222 } else if(!is_focused && was_focused) {
223 //Lost focus.
224 was_enabled = platform::is_sound_enabled();
225 if(!background_audio)
226 platform::sound_enable(false);
228 was_focused = is_focused;
229 cleanup_dead_download_timers();
231 private:
232 bool was_focused;
233 bool was_enabled;
236 class download_timer;
237 std::list<download_timer*> download_timer_gc_queue;
239 class download_timer : public wxTimer
241 public:
242 download_timer(wxwin_mainwindow* main, emulator_instance& _inst)
243 : inst(_inst)
245 w = main;
246 Start(50);
248 void Notify()
250 CHECK_UI_THREAD;
251 if(!w->download_in_progress) {
252 //Received a call with download finish already done. Ignore.
253 return;
255 if(w->download_in_progress->finished) {
256 w->update_statusbar();
257 auto old = w->download_in_progress;
258 w->download_in_progress = NULL;
259 if(old->errormsg != "") {
260 show_message_ok(w, "Error downloading movie", old->errormsg,
261 wxICON_EXCLAMATION);
262 } else {
263 inst.iqueue->queue(CLOADSAVE::ldm.name, "$MEMORY:wxwidgets_download_tmp");
265 delete old;
266 try { download_timer_gc_queue.push_back(this); } catch(...) { Stop(); }
267 } else {
268 w->update_statusbar();
271 private:
272 emulator_instance& inst;
273 wxwin_mainwindow* w;
276 void cleanup_dead_download_timers()
278 for(auto i : download_timer_gc_queue) {
279 i->Stop();
280 delete i;
282 download_timer_gc_queue.clear();
285 void hash_callback(uint64_t left, uint64_t total)
287 wxwin_mainwindow* mwin = main_window;
288 if(left == 0xFFFFFFFFFFFFFFFFULL) {
289 hashing_in_progress = false;
290 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
291 last_update = framerate_regulator::get_utime() - 2000000;
292 return;
294 hashing_in_progress = true;
295 hashing_left = left;
296 hashing_total = total;
297 int64_t this_update = framerate_regulator::get_utime();
298 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
299 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
300 last_update = this_update;
304 std::pair<std::string, std::string> lsplit(std::string l)
306 for(unsigned i = 0; i < l.length() - 3; i++)
307 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
308 return std::make_pair(l.substr(0, i), l.substr(i + 3));
309 return std::make_pair("", l);
312 recentfiles::multirom loadreq_to_multirom(const romload_request& req)
314 recentfiles::multirom r;
315 r.packfile = req.packfile;
316 r.singlefile = req.singlefile;
317 r.core = req.core;
318 r.system = req.system;
319 r.region = req.region;
320 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
321 if(req.files[i] != "") {
322 r.files.resize(i + 1);
323 r.files[i] = req.files[i];
325 return r;
328 class system_menu : public wxMenu
330 public:
331 system_menu(wxWindow* win, emulator_instance& _inst);
332 ~system_menu();
333 void on_select(wxCommandEvent& e);
334 void update(bool light);
335 private:
336 emulator_instance& inst;
337 wxWindow* pwin;
338 void insert_pass(int id, const std::string& label);
339 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
340 wxMenu* lookup_menu(const std::string& key);
341 wxMenuItem* sep;
342 std::map<int, unsigned> action_by_id;
343 std::map<unsigned, wxMenuItem*> item_by_action;
344 std::map<wxMenuItem*, wxMenu*> menu_by_item;
345 std::map<std::string, wxMenu*> submenu_by_name;
346 std::map<std::string, wxMenuItem*> submenui_by_name;
347 std::set<unsigned> toggles;
348 int next_id;
351 wxMenu* system_menu::lookup_menu(const std::string& key)
353 CHECK_UI_THREAD;
354 if(key == "")
355 return this;
356 if(submenu_by_name.count(key))
357 return submenu_by_name[key];
358 //Not found, create.
359 if(!sep)
360 sep = AppendSeparator();
361 auto p = lsplit(key);
362 wxMenu* into = lookup_menu(p.first);
363 submenu_by_name[key] = new wxMenu();
364 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
365 menu_by_item[submenui_by_name[key]] = into;
366 return submenu_by_name[key];
369 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
371 CHECK_UI_THREAD;
372 if(!sep)
373 sep = AppendSeparator();
375 auto p = lsplit(label);
376 wxMenu* into = lookup_menu(p.first);
378 action_by_id[next_id] = id;
379 std::string use_label = p.second + (dots ? "..." : "");
380 if(check) {
381 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
382 toggles.insert(id);
383 } else
384 item_by_action[id] = into->Append(next_id, towxstring(use_label));
385 menu_by_item[item_by_action[id]] = into;
386 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
387 wxCommandEventHandler(system_menu::on_select), NULL, this);
390 void system_menu::insert_pass(int id, const std::string& label)
392 CHECK_UI_THREAD;
393 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
394 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
395 Append(id, towxstring(label));
398 system_menu::system_menu(wxWindow* win, emulator_instance& _inst)
399 : inst(_inst)
401 CHECK_UI_THREAD;
402 pwin = win;
403 insert_pass(wxID_PAUSE, "Pause/Unpause");
404 insert_pass(wxID_FRAMEADVANCE, "Step frame");
405 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
406 insert_pass(wxID_NEXTPOLL, "Step poll");
407 sep = NULL;
410 system_menu::~system_menu()
414 void system_menu::on_select(wxCommandEvent& e)
416 CHECK_UI_THREAD;
417 if(!action_by_id.count(e.GetId()))
418 return;
419 unsigned act_id = action_by_id[e.GetId()];
420 const interface_action* act = NULL;
421 for(auto i : inst.rom->get_actions())
422 if(i->id == act_id) {
423 act = i;
424 break;
426 if(!act)
427 return;
428 try {
429 auto p = prompt_action_params(pwin, act->get_title(), act->params);
430 inst.iqueue->run([this, act_id,p]() { this->inst.rom->execute_action(act_id, p); });
431 } catch(canceled_exception& e) {
432 } catch(std::bad_alloc& e) {
433 OOM_panic();
437 void system_menu::update(bool light)
439 CHECK_UI_THREAD;
440 if(!light) {
441 next_id = wxID_ACTIONS_FIRST;
442 if(sep) {
443 Destroy(sep);
444 sep = NULL;
446 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
447 menu_by_item[i->second]->Destroy(i->second);
448 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
449 menu_by_item[i->second]->Destroy(i->second);
450 action_by_id.clear();
451 item_by_action.clear();
452 menu_by_item.clear();
453 submenu_by_name.clear();
454 submenui_by_name.clear();
455 toggles.clear();
457 for(auto i : inst.rom->get_actions())
458 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
460 for(auto i : item_by_action)
461 i.second->Enable(inst.rom->action_flags(i.first) & 1);
462 for(auto i : toggles)
463 item_by_action[i]->Check(inst.rom->action_flags(i) & 2);
466 std::string munge_name(const std::string& orig)
468 std::string newname;
469 regex_results r;
470 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
471 uint64_t sequence;
472 try {
473 sequence = parse_value<uint64_t>(r[2]);
474 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
475 } catch(...) {
476 newname = newname + "(2)";
478 } else {
479 newname = newname + "(2)";
481 return newname;
484 void handle_watch_load(emulator_instance& inst, std::map<std::string, std::string>& new_watches,
485 std::set<std::string>& old_watches)
487 auto proj = inst.project->get();
488 if(proj) {
489 for(auto i : new_watches) {
490 std::string name = i.first;
491 while(true) {
492 if(!old_watches.count(name)) {
493 try {
494 if(name != "" && i.second != "")
495 inst.mwatch->set(name, i.second);
496 } catch(std::exception& e) {
497 messages << "Can't set memory watch '" << name << "': "
498 << e.what() << std::endl;
500 break;
501 } else if(inst.mwatch->get_string(name) == i.second)
502 break;
503 else
504 name = munge_name(name);
507 } else {
508 for(auto i : new_watches)
509 try {
510 if(i.first != "" && i.second != "")
511 inst.mwatch->set(i.first, i.second);
512 } catch(std::exception& e) {
513 messages << "Can't set memory watch '" << i.first << "': "
514 << e.what() << std::endl;
516 for(auto i : old_watches)
517 if(!new_watches.count(i))
518 try {
519 inst.mwatch->clear(i);
520 } catch(std::exception& e) {
521 messages << "Can't clear memory watch '" << i << "': "
522 << e.what() << std::endl;
527 std::string get_default_screenshot_name(emulator_instance& inst)
529 auto p = inst.project->get();
530 if(!p)
531 return "";
532 else {
533 auto files = directory::enumerate(p->directory, ".*-[0-9]+\\.png");
534 std::set<std::string> numbers;
535 for(auto i : files) {
536 size_t split;
537 #ifdef FUCKED_SYSTEM
538 split = i.find_last_of("\\/");
539 #else
540 split = i.find_last_of("/");
541 #endif
542 std::string name = i;
543 if(split < name.length())
544 name = name.substr(split + 1);
545 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
546 if(r[1] != p->prefix)
547 continue;
548 numbers.insert(r[2]);
550 for(uint64_t i = 1;; i++) {
551 std::string candidate = (stringfmt() << i).str();
552 if(!numbers.count(candidate))
553 return p->prefix + "-" + candidate + ".png";
558 std::string project_prefixname(emulator_instance& inst, const std::string ext)
560 auto p = inst.project->get();
561 if(!p)
562 return "";
563 else
564 return p->prefix + "." + ext;
568 void recent_rom_selected(emulator_instance& inst, const recentfiles::multirom& file)
570 romload_request req;
571 req.packfile = file.packfile;
572 req.singlefile = file.singlefile;
573 req.core = file.core;
574 req.system = file.system;
575 req.region = file.region;
576 for(unsigned i = 0; i < file.files.size() && i < ROM_SLOT_COUNT; i++)
577 req.files[i] = file.files[i];
578 inst.iqueue->run_async([req]() {
579 CORE().command->invoke("unpause-emulator");
580 load_new_rom(req);
581 }, [](std::exception& e) {});
584 void recent_movie_selected(emulator_instance& inst, const recentfiles::path& file)
586 inst.iqueue->queue(CLOADSAVE::ldsm.name, file.get_path());
589 void recent_script_selected(emulator_instance& inst, const recentfiles::path& file)
591 inst.iqueue->queue(CLUA::run.name, file.get_path());
594 wxString getname(emulator_instance& inst)
596 std::string windowname = "lsnes rr" + lsnes_version + " [";
597 auto p = inst.project->get();
598 if(p)
599 windowname = windowname + p->name;
600 else
601 windowname = windowname + inst.rom->get_core_identifier();
602 windowname = windowname + "]";
603 return towxstring(windowname);
606 struct emu_args
608 emulator_instance* inst;
609 struct loaded_rom rom;
610 struct moviefile* initial;
611 bool load_has_to_succeed;
614 void* emulator_main(void* _args)
616 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
617 auto& inst = *args->inst;
618 try {
619 *inst.rom = args->rom;
620 messages << "Using core: " << inst.rom->get_core_identifier() << std::endl;
621 struct moviefile* movie = args->initial;
622 bool has_to_succeed = args->load_has_to_succeed;
623 platform::flush_command_queue();
624 main_loop(*inst.rom, *movie, has_to_succeed);
625 signal_program_exit();
626 } catch(std::bad_alloc& e) {
627 OOM_panic();
628 } catch(std::exception& e) {
629 messages << "FATAL: " << e.what() << std::endl;
630 platform::fatal_error();
632 delete args;
633 return NULL;
636 void join_emulator_thread()
638 emulation_thread->join();
641 bool is_readonly_mode(emulator_instance& inst)
643 bool ret;
644 inst.iqueue->run([&ret]() {
645 ret = *CORE().mlogic ? CORE().mlogic->get_movie().readonly_mode() : false;
647 return ret;
650 void set_speed(emulator_instance& inst, double target)
652 if(target < 0)
653 inst.framerate->set_speed_multiplier(std::numeric_limits<double>::infinity());
654 else
655 inst.framerate->set_speed_multiplier(target / 100);
658 void update_preferences()
660 preferred_core.clear();
661 for(auto i : core_type::get_core_types()) {
662 std::string val = i->get_hname() + " / " + i->get_core_identifier();
663 for(auto j : i->get_extensions()) {
664 std::string key = "ext:" + j;
665 if(core_selections.count(key) && core_selections[key] == val)
666 preferred_core[key] = i;
668 std::string key2 = "type:" + i->get_iname();
669 if(core_selections.count(key2) && core_selections[key2] == val)
670 preferred_core[key2] = i;
674 bool is_lsnes_movie(const std::string& filename)
676 std::istream* s = NULL;
677 try {
678 bool ans = false;
679 s = &zip::openrel(filename, "");
680 char buf[6] = {0};
681 s->read(buf, 5);
682 if(*s && !strcmp(buf, "lsmv\x1A"))
683 ans = true;
684 delete s;
685 if(ans) return true;
686 } catch(...) {
687 delete s;
689 try {
690 zip::reader r(filename);
691 std::istream& s = r["systemid"];
692 std::string s2;
693 std::getline(s, s2);
694 delete &s;
695 istrip_CR(s2);
696 return (s2 == "lsnes-rr1");
697 } catch(...) {
698 return false;
702 class loadfile : public wxFileDropTarget
704 public:
705 loadfile(wxwin_mainwindow* win, emulator_instance& _inst) : inst(_inst), pwin(win) {};
706 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
708 CHECK_UI_THREAD;
709 bool ret = false;
710 if(filenames.Count() == 2) {
711 std::string a = tostdstring(filenames[0]);
712 std::string b = tostdstring(filenames[1]);
713 bool amov = is_lsnes_movie(a);
714 bool bmov = is_lsnes_movie(b);
715 if(amov == bmov)
716 return false;
717 if(amov) std::swap(a, b);
718 inst.iqueue->run_async([a, b]() {
719 CORE().command->invoke("unpause-emulator");
720 romload_request req;
721 req.packfile = a;
722 load_new_rom(req);
723 CORE().command->invoke(CLOADSAVE::ldsm.name, b);
724 }, [](std::exception& e) {});
725 ret = true;
727 if(filenames.Count() == 1) {
728 std::string a = tostdstring(filenames[0]);
729 bool amov = is_lsnes_movie(a);
730 if(amov) {
731 inst.iqueue->queue(CLOADSAVE::ldsm.name, a);
732 pwin->recent_movies->add(a);
733 ret = true;
734 } else {
735 romload_request req;
736 req.packfile = a;
737 inst.iqueue->run_async([req]() {
738 CORE().command->invoke("unpause-emulator");
739 load_new_rom(req);
740 }, [](std::exception& e) {});
741 pwin->recent_roms->add(loadreq_to_multirom(req));
742 ret = true;
745 return ret;
747 emulator_instance& inst;
748 wxwin_mainwindow* pwin;
752 void boot_emulator(emulator_instance& inst, loaded_rom& rom, moviefile& movie, bool fscreen)
754 CHECK_UI_THREAD;
755 update_preferences();
756 try {
757 struct emu_args* a = new emu_args;
758 a->rom = rom;
759 a->initial = &movie;
760 a->load_has_to_succeed = false;
761 a->inst = &inst;
762 modal_pause_holder hld;
763 emulation_thread = new threads::thread(emulator_main, a);
764 main_window = new wxwin_mainwindow(inst, fscreen);
765 main_window->Show();
766 } catch(std::bad_alloc& e) {
767 OOM_panic();
771 wxwin_mainwindow::panel::panel(wxWindow* win, emulator_instance& _inst)
772 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS), inst(_inst)
774 CHECK_UI_THREAD;
775 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
776 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
777 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
778 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
779 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
780 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
781 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
782 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
783 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
784 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
785 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
786 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
787 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
788 SetMinSize(wxSize(512, 448));
791 void wxwin_mainwindow::menu_start(wxString name)
793 CHECK_UI_THREAD;
794 while(!upper.empty())
795 upper.pop();
796 current_menu = new wxMenu();
797 menubar->Append(current_menu, name);
800 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
802 CHECK_UI_THREAD;
803 while(!upper.empty())
804 upper.pop();
805 menubar->Append(menu, name);
806 current_menu = NULL;
809 wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
811 CHECK_UI_THREAD;
812 return current_menu->AppendSubMenu(menu, name);
815 void wxwin_mainwindow::menu_entry(int id, wxString name)
817 CHECK_UI_THREAD;
818 current_menu->Append(id, name);
819 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
820 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
823 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
825 CHECK_UI_THREAD;
826 checkitems[id] = current_menu->AppendCheckItem(id, name);
827 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
828 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
831 void wxwin_mainwindow::menu_start_sub(wxString name)
833 CHECK_UI_THREAD;
834 wxMenu* old = current_menu;
835 upper.push(current_menu);
836 current_menu = new wxMenu();
837 old->AppendSubMenu(current_menu, name);
840 void wxwin_mainwindow::menu_end_sub()
842 current_menu = upper.top();
843 upper.pop();
846 bool wxwin_mainwindow::menu_ischecked(int id)
848 CHECK_UI_THREAD;
849 if(checkitems.count(id))
850 return checkitems[id]->IsChecked();
851 else
852 return false;
855 void wxwin_mainwindow::menu_check(int id, bool newstate)
857 CHECK_UI_THREAD;
858 if(checkitems.count(id))
859 return checkitems[id]->Check(newstate);
860 else
861 return;
864 void wxwin_mainwindow::menu_enable(int id, bool newstate)
866 CHECK_UI_THREAD;
867 auto item = menubar->FindItem(id);
868 if(!item)
869 return;
870 item->Enable(newstate);
873 void wxwin_mainwindow::menu_separator()
875 CHECK_UI_THREAD;
876 current_menu->AppendSeparator();
879 void wxwin_mainwindow::panel::request_paint()
881 CHECK_UI_THREAD;
882 Refresh();
885 std::pair<double, double> calc_scale_factors(double factor, bool ar, double par)
887 if(!ar)
888 return std::make_pair(factor, factor);
889 else if(par < 1) {
890 //Too wide, make taller.
891 return std::make_pair(factor, factor / par);
892 } else {
893 //Too narrow, make wider.
894 return std::make_pair(factor * par, factor);
898 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
900 CHECK_UI_THREAD;
901 if(wx_escape_count >= 3 && is_fs) {
902 //Leave fullscreen mode.
903 main_window->enter_or_leave_fullscreen(false);
905 inst.fbuf->render_framebuffer();
906 static
907 uint8_t* srcp[1];
908 int srcs[1];
909 uint8_t* dstp[1];
910 int dsts[1];
911 wxPaintDC dc(this);
912 uint32_t tw, th;
913 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
914 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled, inst.rom->get_PAR());
915 if(rotate_enabled) {
916 tw = inst.fbuf->main_screen.get_height() * sfactors.second + 0.5;
917 th = inst.fbuf->main_screen.get_width() * sfactors.first + 0.5;
918 } else {
919 tw = inst.fbuf->main_screen.get_width() * sfactors.first + 0.5;
920 th = inst.fbuf->main_screen.get_height() * sfactors.second + 0.5;
922 if(!tw || !th) {
923 main_window_dirty = false;
924 return;
926 //Scale this to fullscreen.
927 if(is_fs) {
928 wxSize screen = main_window->GetSize();
929 double fss = min(1.0 * screen.GetWidth() / tw, 1.0 * screen.GetHeight() / th);
930 tw *= fss;
931 th *= fss;
934 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
935 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
936 if(screen_buffer) {
937 delete[] screen_buffer;
938 screen_buffer = NULL;
940 if(rotate_buffer) {
941 delete[] rotate_buffer;
942 rotate_buffer = NULL;
944 old_height = th;
945 old_width = tw;
946 old_flags = scaling_flags;
947 old_hflip = hflip_enabled;
948 old_vflip = vflip_enabled;
949 old_rotate = rotate_enabled;
950 uint32_t w = inst.fbuf->main_screen.get_width();
951 uint32_t h = inst.fbuf->main_screen.get_height();
952 if(w && h)
953 sws_ctx = sws_getCachedContext(sws_ctx, rotate_enabled ? h : w, rotate_enabled ? w : h,
954 PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
955 tw = max(tw, static_cast<uint32_t>(128));
956 th = max(th, static_cast<uint32_t>(112));
957 screen_buffer = new unsigned char[tw * th * 3 + 64];
958 if(aux)
959 rotate_buffer = new uint32_t[inst.fbuf->main_screen.get_width() *
960 inst.fbuf->main_screen.get_height()];
961 SetMinSize(wxSize(tw, th));
962 signal_resize_needed();
964 if(aux) {
965 //Hflip, Vflip or rotate active.
966 size_t width = inst.fbuf->main_screen.get_width();
967 size_t height = inst.fbuf->main_screen.get_height();
968 size_t width1 = width - 1;
969 size_t height1 = height - 1;
970 size_t stride = inst.fbuf->main_screen.rowptr(1) - inst.fbuf->main_screen.rowptr(0);
971 uint32_t* pixels = inst.fbuf->main_screen.rowptr(0);
972 if(rotate_enabled) {
973 for(unsigned y = 0; y < height; y++) {
974 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
975 uint32_t* dpixels = rotate_buffer + (height1 - y);
976 if(hflip_enabled)
977 for(unsigned x = 0; x < width; x++)
978 dpixels[x * height] = pixels2[width1 - x];
979 else
980 for(unsigned x = 0; x < width; x++)
981 dpixels[x * height] = pixels2[x];
983 } else {
984 for(unsigned y = 0; y < height; y++) {
985 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
986 uint32_t* dpixels = rotate_buffer + y * width;
987 if(hflip_enabled)
988 for(unsigned x = 0; x < width; x++)
989 dpixels[x] = pixels2[width1 - x];
990 else
991 for(unsigned x = 0; x < width; x++)
992 dpixels[x] = pixels2[x];
996 if(aux)
997 srcs[0] = 4 * (rotate_enabled ? inst.fbuf->main_screen.get_height() :
998 inst.fbuf->main_screen.get_width());
999 else
1000 srcs[0] = 4 * inst.fbuf->main_screen.get_stride();
1001 dsts[0] = 3 * tw;
1002 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : inst.fbuf->main_screen.rowptr(0));
1003 dstp[0] = screen_buffer;
1004 memset(screen_buffer, 0, tw * th * 3);
1005 if(inst.fbuf->main_screen.get_width() && inst.fbuf->main_screen.get_height())
1006 sws_scale(sws_ctx, srcp, srcs, 0, rotate_enabled ? inst.fbuf->main_screen.get_width() :
1007 inst.fbuf->main_screen.get_height(),
1008 dstp, dsts);
1009 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
1010 dc.DrawBitmap(bmp, 0, 0, false);
1011 main_window_dirty = false;
1012 main_window->update_statusbar();
1015 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
1017 //Blank.
1020 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
1022 CHECK_UI_THREAD;
1023 handle_wx_keyboard(inst, e, true);
1026 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
1028 CHECK_UI_THREAD;
1029 handle_wx_keyboard(inst, e, false);
1032 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
1034 CHECK_UI_THREAD;
1035 handle_wx_mouse(inst, e);
1038 wxwin_mainwindow::wxwin_mainwindow(emulator_instance& _inst, bool fscreen)
1039 : wxFrame(NULL, wxID_ANY, getname(_inst), wxDefaultPosition, wxSize(-1, -1),
1040 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX), inst(_inst)
1042 CHECK_UI_THREAD;
1043 download_in_progress = NULL;
1044 Centre();
1045 mwindow = NULL;
1046 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
1047 toplevel->Add(gpanel = new panel(this, inst), 1, wxGROW);
1048 toplevel->Add(spanel = new wxwin_status::panel(this, inst, gpanel, 20), 1, wxGROW);
1049 spanel_shown = true;
1050 toplevel->SetSizeHints(this);
1051 SetSizer(toplevel);
1052 Fit();
1053 gpanel->SetFocus();
1054 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
1055 SetMenuBar(menubar = new wxMenuBar);
1056 SetStatusBar(statusbar = new wxStatusBar(this));
1058 menu_start(wxT("File"));
1059 menu_start_sub(wxT("New"));
1060 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
1061 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
1062 menu_end_sub();
1063 menu_start_sub(wxT("Load"));
1064 menu_entry(wxID_LOAD_STATE, wxT("State..."));
1065 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
1066 menu_entry(wxID_DOWNLOAD, wxT("Download movie..."));
1067 if(loadlib::library::name() != "") {
1068 menu_separator();
1069 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name()));
1070 menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager"));
1072 menu_separator();
1073 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
1074 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST, wxT("ROM..."));
1075 menu_special_sub(wxT("Multifile ROM"), loadroms = new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST + 1,
1076 wxID_LOAD_ROM_IMAGE_LAST, [this](core_type* t) { this->do_load_rom_image(t); }));
1077 menu_special_sub(wxT("Project"), projects = new projects_menu(this, inst, wxID_PROJECT_FIRST,
1078 wxID_PROJECT_LAST, get_config_path() + "/recent-projects.txt", [this](const std::string& id) {
1079 this->project_selected(id); }));
1080 menu_separator();
1081 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu<recentfiles::multirom>(this, inst,
1082 wxID_RROM_FIRST, wxID_RROM_LAST, get_config_path() + "/recent-roms.txt", recent_rom_selected));
1083 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu<recentfiles::path>(this, inst,
1084 wxID_RMOVIE_FIRST, wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt",
1085 recent_movie_selected));
1086 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts = new recent_menu<recentfiles::path>(this, inst,
1087 wxID_RLUA_FIRST, wxID_RLUA_LAST, get_config_path() + "/recent-scripts.txt",
1088 recent_script_selected));
1089 menu_separator();
1090 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
1091 menu_separator();
1092 branches_menu* brlist;
1093 auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, inst,
1094 wxID_BRANCH_FIRST, wxID_BRANCH_LAST));
1095 brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
1096 brlist->update();
1097 menu_end_sub();
1098 menu_start_sub(wxT("Save"));
1099 menu_entry(wxID_SAVE_STATE, wxT("State..."));
1100 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
1101 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
1102 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
1103 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
1104 menu_separator();
1105 menu_entry(wxID_CHDIR, wxT("Change working directory..."));
1106 menu_separator();
1107 menu_special_sub(wxT("Upload"), new upload_menu(this, inst, wxID_UPLOAD_FIRST, wxID_UPLOAD_LAST));
1108 menu_end_sub();
1109 menu_start_sub(wxT("Close"));
1110 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
1111 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
1112 menu_enable(wxID_CLOSE_PROJECT, inst.project->get() != NULL);
1113 menu_enable(wxID_CLOSE_ROM, inst.project->get() == NULL);
1114 menu_end_sub();
1115 menu_separator();
1116 menu_entry(wxID_EXIT, wxT("Quit"));
1118 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this, inst)));
1120 menu_start(wxT("Movie"));
1121 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
1122 menu_check(wxID_READONLY_MODE, is_readonly_mode(inst));
1123 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
1124 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
1125 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
1126 menu_separator();
1127 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
1129 menu_start(wxT("Speed"));
1130 menu_entry(wxID_SPEED_5, wxT("1/20x"));
1131 menu_entry(wxID_SPEED_10, wxT("1/10x"));
1132 menu_entry(wxID_SPEED_17, wxT("1/6x"));
1133 menu_entry(wxID_SPEED_20, wxT("1/5x"));
1134 menu_entry(wxID_SPEED_25, wxT("1/4x"));
1135 menu_entry(wxID_SPEED_33, wxT("1/3x"));
1136 menu_entry(wxID_SPEED_50, wxT("1/2x"));
1137 menu_entry(wxID_SPEED_100, wxT("1x"));
1138 menu_entry(wxID_SPEED_150, wxT("1.5x"));
1139 menu_entry(wxID_SPEED_200, wxT("2x"));
1140 menu_entry(wxID_SPEED_300, wxT("3x"));
1141 menu_entry(wxID_SPEED_500, wxT("5x"));
1142 menu_entry(wxID_SPEED_1000, wxT("10x"));
1143 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
1144 menu_entry(wxID_SET_SPEED, wxT("Set..."));
1146 menu_start(wxT("Tools"));
1147 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
1148 menu_separator();
1149 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
1150 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
1151 menu_separator();
1152 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
1153 menu_separator();
1154 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
1155 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
1156 menu_entry(wxID_MULTITRACK, wxT("Multitrack..."));
1157 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
1158 menu_separator();
1159 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
1160 menu_separator();
1161 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
1162 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
1163 menu_separator();
1164 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
1165 menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
1166 tracelog_menu* trlog;
1167 auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, inst,
1168 wxID_TRACELOG_FIRST, wxID_TRACELOG_LAST));
1169 trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
1170 trlog->update();
1171 menu_entry(wxID_DISASSEMBLER, wxT("Disassembler..."));
1172 menu_separator();
1173 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
1174 menu_separator();
1175 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
1176 inst, wxID_DUMP_FIRST, wxID_DUMP_LAST)));
1178 menu_start(wxT("Configure"));
1179 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
1180 menu_check(wxID_SHOW_STATUS, true);
1181 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
1182 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
1183 menu_special_sub(wxT("Settings"), new settings_menu(this, inst, wxID_SETTINGS_FIRST));
1184 if(audioapi_driver_initialized()) {
1185 menu_separator();
1186 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
1187 menu_entry(wxID_VUDISPLAY, wxT("VU display / sound controls"));
1188 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
1190 menu_separator();
1191 menu_entry(wxID_ENTER_FULLSCREEN, wxT("Enter fullscreen mode"));
1193 menu_start(wxT("Help"));
1194 menu_entry(wxID_ABOUT, wxT("About..."));
1196 corechange.set(inst.dispatch->core_change, []() { signal_core_change(); });
1197 titlechange.set(inst.dispatch->title_change, []() { signal_core_change(); });
1198 newcore.set(notify_new_core, []() { update_preferences(); });
1199 unmuted.set(inst.dispatch->sound_unmute, [this](bool unmute) {
1200 runuifun([this, unmute]() { this->menu_check(wxID_AUDIO_ENABLED, unmute); });
1202 modechange.set(inst.dispatch->mode_change, [this](bool readonly) {
1203 runuifun([this, readonly]() { this->menu_check(wxID_READONLY_MODE, readonly); });
1205 gpanel->SetDropTarget(new loadfile(this, inst));
1206 spanel->SetDropTarget(new loadfile(this, inst));
1207 set_hasher_callback(hash_callback);
1208 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1209 menubar->SetMenuLabel(1, towxstring(inst.rom->get_systemmenu_name()));
1210 focus_timer = new _focus_timer;
1211 status_timer = new _status_timer;
1212 if(fscreen) {
1213 wx_escape_count = 0;
1214 enter_or_leave_fullscreen(true);
1218 wxwin_mainwindow::~wxwin_mainwindow()
1220 CHECK_UI_THREAD;
1221 if(sws_ctx) sws_freeContext(sws_ctx);
1222 if(screen_buffer) delete[] screen_buffer;
1223 if(rotate_buffer) delete[] rotate_buffer;
1224 focus_timer->Stop();
1225 delete focus_timer;
1226 status_timer->Stop();
1227 delete status_timer;
1230 void wxwin_mainwindow::request_paint()
1232 CHECK_UI_THREAD;
1233 gpanel->Refresh();
1236 void wxwin_mainwindow::on_close(wxCloseEvent& e)
1238 CHECK_UI_THREAD;
1239 //Veto it for now, latter things will delete it.
1240 e.Veto();
1241 inst.iqueue->queue("quit-emulator");
1244 void wxwin_mainwindow::notify_update() throw()
1246 CHECK_UI_THREAD;
1247 if(!main_window_dirty) {
1248 main_window_dirty = true;
1249 gpanel->Refresh();
1253 void wxwin_mainwindow::notify_resized() throw()
1255 CHECK_UI_THREAD;
1256 toplevel->Layout();
1257 toplevel->SetSizeHints(this);
1258 Fit();
1261 void wxwin_mainwindow::notify_update_status() throw()
1263 CHECK_UI_THREAD;
1264 spanel->request_paint();
1265 if(mwindow)
1266 mwindow->notify_update();
1267 status_updated = true;
1270 void wxwin_mainwindow::notify_exit() throw()
1272 CHECK_UI_THREAD;
1273 wxwidgets_exiting = true;
1274 join_emulator_thread();
1275 Destroy();
1278 std::string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1280 if(!vars.count(key))
1281 return "";
1282 return utf8::to8(vars.find(key)->second);
1285 void wxwin_mainwindow::update_statusbar()
1287 CHECK_UI_THREAD;
1288 if(download_in_progress) {
1289 statusbar->SetStatusText(towxstring(download_in_progress->statusmsg()));
1290 return;
1292 if(hashing_in_progress) {
1293 //TODO: Display this as a dialog.
1294 std::ostringstream s;
1295 s << "Hashing ROMs, approximately " << ((hashing_left + 524288) >> 20) << " of "
1296 << ((hashing_total + 524288) >> 20) << "MB left...";
1297 statusbar->SetStatusText(towxstring(s.str()));
1298 return;
1300 auto& vars = inst.status->get_read();
1301 if(!vars.valid) {
1302 inst.status->put_read();
1303 return;
1305 try {
1306 std::ostringstream s;
1307 bool recording = (vars.mode == 'R');
1308 if(vars.movie_valid) {
1309 if(recording)
1310 s << "Frame: " << vars.curframe;
1311 else
1312 s << "Frame: " << vars.curframe << "/" << vars.length;
1313 s << " Lag: " << vars.lag;
1314 if(vars.subframe == _lsnes_status::subframe_savepoint)
1315 s << " Subframe: S";
1316 else if(vars.subframe == _lsnes_status::subframe_video)
1317 s << " Subframe: V";
1318 else
1319 s << " Subframe: " << vars.subframe;
1320 } else {
1321 s << "Frame: N/A Lag: N/A Subframe: N/A";
1323 if(vars.saveslot_valid) {
1324 s << " Slot: ";
1325 if(vars.branch_valid) s << utf8::to8(vars.branch) << "ā†’";
1326 s << vars.saveslot;
1327 s << " [" << utf8::to8(vars.slotinfo) << "]";
1329 s << " Speed: " << vars.speed << "% ";
1330 if(vars.pause == _lsnes_status::pause_break)
1331 s << " Breakpoint";
1332 else if(vars.pause == _lsnes_status::pause_normal)
1333 s << " Paused";
1334 if(vars.dumping)
1335 s << " Dumping";
1336 if(vars.mode == 'C')
1337 s << " Corrupt";
1338 else if(vars.mode == 'R')
1339 s << " Recording";
1340 else if(vars.mode == 'P')
1341 s << " Playback";
1342 else if(vars.mode == 'F')
1343 s << " Finished";
1344 else
1345 s << " Unknown";
1346 if(vars.mbranch_valid)
1347 s << " Branch: " << utf8::to8(vars.mbranch);
1348 std::string macros = utf8::to8(vars.macros);
1349 if(macros.length())
1350 s << " Macros: " << macros;
1352 statusbar->SetStatusText(towxstring(s.str()));
1353 } catch(std::exception& e) {
1355 inst.status->put_read();
1358 #define NEW_KEYBINDING "A new binding..."
1359 #define NEW_ALIAS "A new alias..."
1360 #define NEW_WATCH "A new watch..."
1362 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1364 CHECK_UI_THREAD;
1365 try {
1366 handle_menu_click_cancelable(e);
1367 } catch(canceled_exception& e) {
1368 //Ignore.
1369 } catch(std::bad_alloc& e) {
1370 OOM_panic();
1371 } catch(std::exception& e) {
1372 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1376 void wxwin_mainwindow::refresh_title() throw()
1378 CHECK_UI_THREAD;
1379 SetTitle(getname(inst));
1380 auto p = inst.project->get();
1381 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1382 for(int i = wxID_LOAD_ROM_IMAGE_FIRST; i <= wxID_LOAD_ROM_IMAGE_LAST; i++)
1383 menu_enable(i, !p);
1384 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1385 menu_enable(wxID_CLOSE_ROM, p == NULL);
1386 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1387 menubar->SetMenuLabel(1, towxstring(inst.rom->get_systemmenu_name()));
1390 namespace
1392 struct movie_or_savestate
1394 public:
1395 typedef std::pair<std::string,std::string> returntype;
1396 movie_or_savestate(emulator_instance& _inst, bool is_state)
1397 : inst(_inst)
1399 state = is_state;
1401 filedialog_input_params input(bool save) const
1403 filedialog_input_params p;
1404 std::string ext = state ? inst.project->savestate_ext() : "lsmv";
1405 std::string name = state ? "Savestates" : "Movies";
1406 if(save) {
1407 p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
1408 p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
1409 } else
1410 p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
1411 ext));
1412 if(!save && state) {
1413 p.types.push_back(filedialog_type_entry("Savestates [playback]", "*." + ext +
1414 ";*." + ext + ".backup", ext));
1415 p.types.push_back(filedialog_type_entry("Savestates [recording]", "*." + ext +
1416 ";*." + ext + ".backup", ext));
1417 p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
1418 ";*." + ext + ".backup", ext));
1419 p.types.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext +
1420 ";*." + ext + ".backup", ext));
1422 p.default_type = save ? (state ? save_dflt_binary(*inst.settings) :
1423 movie_dflt_binary(*inst.settings)) : 0;
1424 return p;
1426 std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
1428 std::string cmdmod;
1429 if(save)
1430 cmdmod = p.typechoice ? "-binary" : "-zip";
1431 else if(state)
1432 switch(p.typechoice) {
1433 case 0: cmdmod = ""; break;
1434 case 1: cmdmod = "-readonly"; break;
1435 case 2: cmdmod = "-state"; break;
1436 case 3: cmdmod = "-preserve"; break;
1437 case 4: cmdmod = "-allbranches"; break;
1439 return std::make_pair(cmdmod, p.path);
1441 private:
1442 emulator_instance& inst;
1443 bool state;
1445 struct movie_or_savestate filetype_movie(lsnes_instance, false);
1446 struct movie_or_savestate filetype_savestate(lsnes_instance, true);
1449 void wxwin_mainwindow::project_selected(const std::string& id)
1451 std::string filename, displayname;
1452 bool load_ok = false;
1453 inst.iqueue->run([id, &filename, &displayname, &load_ok]() -> void {
1454 try {
1455 auto& p = CORE().project->load(id); //Check.
1456 filename = p.filename;
1457 displayname = p.name;
1458 load_ok = true;
1459 delete &p;
1460 switch_projects(id);
1461 } catch(std::exception& e) {
1462 messages << "Failed to change project: " << e.what() << std::endl;
1465 if(load_ok) {
1466 recentfiles::namedobj obj;
1467 obj._id = id;
1468 obj._filename = filename;
1469 obj._display = displayname;
1470 projects->add(obj);
1474 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1476 CHECK_UI_THREAD;
1477 std::string filename;
1478 std::pair<std::string, std::string> filename2;
1479 bool s;
1480 switch(e.GetId()) {
1481 case wxID_FRAMEADVANCE:
1482 inst.iqueue->queue("+advance-frame");
1483 inst.iqueue->queue("-advance-frame");
1484 return;
1485 case wxID_SUBFRAMEADVANCE:
1486 inst.iqueue->queue("+advance-poll");
1487 inst.iqueue->queue("-advance-poll");
1488 return;
1489 case wxID_NEXTPOLL:
1490 inst.iqueue->queue("advance-skiplag");
1491 return;
1492 case wxID_PAUSE:
1493 inst.iqueue->queue("pause-emulator");
1494 return;
1495 case wxID_EXIT:
1496 inst.iqueue->queue("quit-emulator");
1497 return;
1498 case wxID_AUDIO_ENABLED:
1499 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1500 return;
1501 case wxID_CANCEL_SAVES:
1502 inst.iqueue->queue("cancel-saves");
1503 return;
1504 case wxID_LOAD_MOVIE:
1505 filename = choose_file_load(this, "Load Movie", UI_get_project_moviepath(inst),
1506 filetype_movie).second;
1507 recent_movies->add(filename);
1508 inst.iqueue->queue(CLOADSAVE::ldm.name, filename);
1509 return;
1510 case wxID_LOAD_STATE:
1511 filename2 = choose_file_load(this, "Load State", UI_get_project_moviepath(inst),
1512 filetype_savestate);
1513 recent_movies->add(filename2.second);
1514 inst.iqueue->queue("load" + filename2.first + " " + filename2.second);
1515 return;
1516 case wxID_REWIND_MOVIE:
1517 inst.iqueue->queue("rewind-movie");
1518 return;
1519 case wxID_SAVE_MOVIE:
1520 filename2 = choose_file_save(this, "Save Movie", UI_get_project_moviepath(inst), filetype_movie,
1521 project_prefixname(inst, "lsmv"));
1522 recent_movies->add(filename2.second);
1523 inst.iqueue->queue("save-movie" + filename2.first + " " + filename2.second);
1524 return;
1525 case wxID_SAVE_SUBTITLES:
1526 inst.iqueue->queue(CSUBTITLE::save.name, choose_file_save(this, "Save subtitles",
1527 UI_get_project_moviepath(inst), filetype_sub, project_prefixname(inst, "sub")));
1528 return;
1529 case wxID_SAVE_STATE:
1530 filename2 = choose_file_save(this, "Save State", UI_get_project_moviepath(inst),
1531 filetype_savestate);
1532 recent_movies->add(filename2.second);
1533 inst.iqueue->queue("save-state" + filename2.first + " " + filename2.second);
1534 return;
1535 case wxID_SAVE_SCREENSHOT:
1536 inst.iqueue->queue(CFRAMEBUF::ss.name, choose_file_save(this, "Save Screenshot",
1537 UI_get_project_moviepath(inst), filetype_png, get_default_screenshot_name(inst)));
1538 return;
1539 case wxID_RUN_SCRIPT:
1540 inst.iqueue->queue(CLUA::run.name, pick_file_member(this, "Select Script",
1541 UI_get_project_otherpath(inst)));
1542 return;
1543 case wxID_RUN_LUA: {
1544 std::string f = choose_file_load(this, "Select Lua Script", UI_get_project_otherpath(inst),
1545 filetype_lua_script);
1546 inst.iqueue->queue(CLUA::run.name, f);
1547 recent_scripts->add(f);
1548 return;
1550 case wxID_RESET_LUA:
1551 inst.iqueue->queue(CLUA::reset.name);
1552 return;
1553 case wxID_EVAL_LUA:
1554 inst.iqueue->queue(CLUA::eval.name, pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1555 return;
1556 case wxID_READONLY_MODE:
1557 s = menu_ischecked(wxID_READONLY_MODE);
1558 inst.iqueue->run([s]() {
1559 auto& core = CORE();
1560 if(!s)
1561 core.lua2->callback_movie_lost("readwrite");
1562 if(*core.mlogic) core.mlogic->get_movie().readonly_mode(s);
1563 core.dispatch->mode_change(s);
1564 if(!s)
1565 core.lua2->callback_do_readwrite();
1566 core.supdater->update();
1567 core.dispatch->status_update();
1569 return;
1570 case wxID_AUTOHOLD:
1571 wxeditor_autohold_display(this, inst);
1572 return;
1573 case wxID_EDIT_AUTHORS:
1574 wxeditor_authors_display(this, inst);
1575 return;
1576 case wxID_EDIT_MACROS:
1577 wxeditor_macro_display(this, inst);
1578 return;
1579 case wxID_EDIT_SUBTITLES:
1580 wxeditor_subtitles_display(this, inst);
1581 return;
1582 case wxID_EDIT_VSUBTITLES:
1583 show_wxeditor_voicesub(this, inst);
1584 return;
1585 case wxID_EDIT_MEMORYWATCH:
1586 wxeditor_memorywatches_display(this, inst);
1587 return;
1588 case wxID_SAVE_MEMORYWATCH: {
1589 modal_pause_holder hld;
1590 std::set<std::string> old_watches;
1591 inst.iqueue->run([&old_watches]() { old_watches = CORE().mwatch->enumerate(); });
1592 std::string filename = choose_file_save(this, "Save watches to file",
1593 UI_get_project_otherpath(inst), filetype_watch);
1594 std::ofstream out(filename.c_str());
1595 for(auto i : old_watches) {
1596 std::string val;
1597 inst.iqueue->run([i, &val]() {
1598 try {
1599 val = CORE().mwatch->get_string(i);
1600 } catch(std::exception& e) {
1601 messages << "Can't get value of watch '" << i << "': " << e.what()
1602 << std::endl;
1605 out << i << std::endl << val << std::endl;
1607 out.close();
1608 return;
1610 case wxID_LOAD_MEMORYWATCH: {
1611 modal_pause_holder hld;
1612 std::set<std::string> old_watches;
1613 inst.iqueue->run([&old_watches]() { old_watches = CORE().mwatch->enumerate(); });
1614 std::map<std::string, std::string> new_watches;
1615 std::string filename = choose_file_load(this, "Choose memory watch file",
1616 UI_get_project_otherpath(inst), filetype_watch);
1617 try {
1618 std::istream& in = zip::openrel(filename, "");
1619 while(in) {
1620 std::string wname;
1621 std::string wexpr;
1622 std::getline(in, wname);
1623 std::getline(in, wexpr);
1624 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1626 delete &in;
1627 } catch(std::exception& e) {
1628 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1629 wxICON_EXCLAMATION);
1630 return;
1633 inst.iqueue->run([this, &new_watches, &old_watches]() {
1634 handle_watch_load(this->inst, new_watches, old_watches);
1636 return;
1638 case wxID_MEMORY_SEARCH:
1639 wxwindow_memorysearch_display(inst);
1640 return;
1641 case wxID_TASINPUT:
1642 wxeditor_tasinput_display(this, inst);
1643 return;
1644 case wxID_ABOUT: {
1645 std::ostringstream str;
1646 str << "Version: lsnes rr" << lsnes_version << std::endl;
1647 str << "Revision: " << lsnes_git_revision << std::endl;
1648 for(auto i : core_core::all_cores())
1649 if(!i->is_hidden())
1650 str << "Core: " << i->get_core_identifier() << std::endl;
1651 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1652 return;
1654 case wxID_SHOW_STATUS: {
1655 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1656 if(newstate)
1657 spanel->Show();
1658 if(newstate && !spanel_shown)
1659 toplevel->Add(spanel, 1, wxGROW);
1660 else if(!newstate && spanel_shown)
1661 toplevel->Detach(spanel);
1662 if(!newstate)
1663 spanel->Hide();
1664 spanel_shown = newstate;
1665 toplevel->Layout();
1666 toplevel->SetSizeHints(this);
1667 Fit();
1668 return;
1670 case wxID_DEDICATED_MEMORY_WATCH: {
1671 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1672 if(newstate && !mwindow) {
1673 mwindow = new wxwin_status(-1, inst, "Memory Watch");
1674 spanel->set_watch_flag(1);
1675 mwindow->Show();
1676 } else if(!newstate && mwindow) {
1677 mwindow->Destroy();
1678 mwindow = NULL;
1679 spanel->set_watch_flag(0);
1681 return;
1683 case wxID_SET_SPEED: {
1684 std::string value = "infinite";
1685 double val = inst.framerate->get_speed_multiplier();
1686 if(!(val == std::numeric_limits<double>::infinity()))
1687 value = (stringfmt() << (100 * val)).str();
1688 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1689 try {
1690 if(value == "infinite")
1691 inst.framerate->set_speed_multiplier(
1692 std::numeric_limits<double>::infinity());
1693 else {
1694 double v = parse_value<double>(value) / 100;
1695 if(v <= 0.0001)
1696 throw 42;
1697 inst.framerate->set_speed_multiplier(v);
1699 } catch(...) {
1700 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1702 return;
1704 case wxID_SPEED_5:
1705 set_speed(inst, 5);
1706 break;
1707 case wxID_SPEED_10:
1708 set_speed(inst, 10);
1709 break;
1710 case wxID_SPEED_17:
1711 set_speed(inst, 16.66666666666);
1712 break;
1713 case wxID_SPEED_20:
1714 set_speed(inst, 20);
1715 break;
1716 case wxID_SPEED_25:
1717 set_speed(inst, 25);
1718 break;
1719 case wxID_SPEED_33:
1720 set_speed(inst, 33.3333333333333);
1721 break;
1722 case wxID_SPEED_50:
1723 set_speed(inst, 50);
1724 break;
1725 case wxID_SPEED_100:
1726 set_speed(inst, 100);
1727 break;
1728 case wxID_SPEED_150:
1729 set_speed(inst, 150);
1730 break;
1731 case wxID_SPEED_200:
1732 set_speed(inst, 200);
1733 break;
1734 case wxID_SPEED_300:
1735 set_speed(inst, 300);
1736 break;
1737 case wxID_SPEED_500:
1738 set_speed(inst, 500);
1739 break;
1740 case wxID_SPEED_1000:
1741 set_speed(inst, 1000);
1742 break;
1743 case wxID_SPEED_TURBO:
1744 set_speed(inst, -1);
1745 break;
1746 case wxID_LOAD_LIBRARY: {
1747 std::string name = std::string("load ") + loadlib::library::name();
1748 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name,
1749 UI_get_project_otherpath(inst), single_type(loadlib::library::extension(),
1750 loadlib::library::name())))));
1751 handle_post_loadlibrary();
1752 break;
1754 case wxID_PLUGIN_MANAGER:
1755 wxeditor_plugin_manager_display(this);
1756 return;
1757 case wxID_RELOAD_ROM_IMAGE:
1758 inst.iqueue->run([]() {
1759 CORE().command->invoke("unpause-emulator");
1760 reload_current_rom();
1762 return;
1763 case wxID_NEW_MOVIE:
1764 show_projectwindow(this, inst);
1765 return;
1766 case wxID_SHOW_MESSAGES:
1767 msg_window->reshow();
1768 return;
1769 case wxID_CONFLICTRESOLUTION:
1770 show_conflictwindow(this);
1771 return;
1772 case wxID_VUDISPLAY:
1773 open_vumeter_window(this, inst);
1774 return;
1775 case wxID_DISASSEMBLER:
1776 wxeditor_disassembler_display(this, inst);
1777 return;
1778 case wxID_MOVIE_EDIT:
1779 wxeditor_movie_display(this, inst);
1780 return;
1781 case wxID_NEW_PROJECT:
1782 open_new_project_window(this, inst);
1783 return;
1784 case wxID_CLOSE_PROJECT:
1785 inst.iqueue->run([this]() -> void { this->inst.project->set(NULL); });
1786 return;
1787 case wxID_CLOSE_ROM:
1788 inst.iqueue->run([]() -> void { close_rom(); });
1789 return;
1790 case wxID_ENTER_FULLSCREEN:
1791 wx_escape_count = 0;
1792 enter_or_leave_fullscreen(true);
1793 return;
1794 case wxID_LOAD_ROM_IMAGE_FIRST:
1795 do_load_rom_image(NULL);
1796 return;
1797 case wxID_HEXEDITOR:
1798 wxeditor_hexedit_display(this, inst);
1799 return;
1800 case wxID_MULTITRACK:
1801 wxeditor_multitrack_display(this, inst);
1802 return;
1803 case wxID_CHDIR: {
1804 wxDirDialog* d = new wxDirDialog(this, wxT("Change working directory"), wxT("."),
1805 wxDD_DIR_MUST_EXIST);
1806 if(d->ShowModal() == wxID_CANCEL) {
1807 d->Destroy();
1808 return;
1810 std::string path = tostdstring(d->GetPath());
1811 d->Destroy();
1812 chdir(path.c_str());
1813 messages << "Changed working directory to '" << path << "'" << std::endl;
1814 return;
1816 case wxID_DOWNLOAD: {
1817 if(download_in_progress) return;
1818 filename = pick_text(this, "Download movie", "Enter URL to download");
1819 download_in_progress = new file_download();
1820 download_in_progress->url = lsnes_uri_rewrite(filename);
1821 download_in_progress->target_slot = "wxwidgets_download_tmp";
1822 download_in_progress->do_async(*inst.rom);
1823 new download_timer(this, inst);
1824 return;
1829 void wxwin_mainwindow::action_updated()
1831 runuifun([this]() { reinterpret_cast<system_menu*>(sysmenu)->update(true); });
1834 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs)
1836 CHECK_UI_THREAD;
1837 if(fs && !is_fs) {
1838 if(spanel_shown)
1839 toplevel->Detach(spanel);
1840 spanel->Hide();
1841 is_fs = fs;
1842 ShowFullScreen(true);
1843 Fit();
1844 } else if(!fs && is_fs) {
1845 ShowFullScreen(false);
1846 if(spanel_shown) {
1847 spanel->Show();
1848 toplevel->Add(spanel, 1, wxGROW);
1850 Fit();
1851 is_fs = fs;