Track all window size changes while in fullscreen
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob18d0bbf47889c8625b002add304897eb6d3bdf11
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;
184 bool becoming_fullscreen = false;
185 wxSize current_resolution;
187 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> background_audio(*lsnes_instance.settings,
188 "background-audio", "GUIā€£Enable background audio", true);
190 class _status_timer : public wxTimer
192 public:
193 _status_timer()
195 Start(50);
197 void Notify()
199 if(status_updated) {
200 status_updated = false;
201 if(main_window) main_window->update_statusbar();
206 void cleanup_dead_download_timers();
207 class _focus_timer : public wxTimer
209 public:
210 _focus_timer()
212 was_focused = (wxWindow::FindFocus() != NULL);
213 was_enabled = platform::is_sound_enabled();
214 Start(500);
216 void Notify()
218 CHECK_UI_THREAD;
219 bool is_focused = (wxWindow::FindFocus() != NULL);
220 if(is_focused && !was_focused) {
221 //Gained focus.
222 if(!background_audio)
223 platform::sound_enable(was_enabled);
224 } else if(!is_focused && was_focused) {
225 //Lost focus.
226 was_enabled = platform::is_sound_enabled();
227 if(!background_audio)
228 platform::sound_enable(false);
230 was_focused = is_focused;
231 cleanup_dead_download_timers();
233 private:
234 bool was_focused;
235 bool was_enabled;
238 class download_timer;
239 std::list<download_timer*> download_timer_gc_queue;
241 class download_timer : public wxTimer
243 public:
244 download_timer(wxwin_mainwindow* main, emulator_instance& _inst)
245 : inst(_inst)
247 w = main;
248 Start(50);
250 void Notify()
252 CHECK_UI_THREAD;
253 if(!w->download_in_progress) {
254 //Received a call with download finish already done. Ignore.
255 return;
257 if(w->download_in_progress->finished) {
258 w->update_statusbar();
259 auto old = w->download_in_progress;
260 w->download_in_progress = NULL;
261 if(old->errormsg != "") {
262 show_message_ok(w, "Error downloading movie", old->errormsg,
263 wxICON_EXCLAMATION);
264 } else {
265 inst.iqueue->queue(CLOADSAVE::ldm.name, "$MEMORY:wxwidgets_download_tmp");
267 delete old;
268 try { download_timer_gc_queue.push_back(this); } catch(...) { Stop(); }
269 } else {
270 w->update_statusbar();
273 private:
274 emulator_instance& inst;
275 wxwin_mainwindow* w;
278 void cleanup_dead_download_timers()
280 for(auto i : download_timer_gc_queue) {
281 i->Stop();
282 delete i;
284 download_timer_gc_queue.clear();
287 void hash_callback(uint64_t left, uint64_t total)
289 wxwin_mainwindow* mwin = main_window;
290 if(left == 0xFFFFFFFFFFFFFFFFULL) {
291 hashing_in_progress = false;
292 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
293 last_update = framerate_regulator::get_utime() - 2000000;
294 return;
296 hashing_in_progress = true;
297 hashing_left = left;
298 hashing_total = total;
299 int64_t this_update = framerate_regulator::get_utime();
300 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
301 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
302 last_update = this_update;
306 std::pair<std::string, std::string> lsplit(std::string l)
308 for(unsigned i = 0; i < l.length() - 3; i++)
309 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
310 return std::make_pair(l.substr(0, i), l.substr(i + 3));
311 return std::make_pair("", l);
314 recentfiles::multirom loadreq_to_multirom(const romload_request& req)
316 recentfiles::multirom r;
317 r.packfile = req.packfile;
318 r.singlefile = req.singlefile;
319 r.core = req.core;
320 r.system = req.system;
321 r.region = req.region;
322 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
323 if(req.files[i] != "") {
324 r.files.resize(i + 1);
325 r.files[i] = req.files[i];
327 return r;
330 class system_menu : public wxMenu
332 public:
333 system_menu(wxWindow* win, emulator_instance& _inst);
334 ~system_menu();
335 void on_select(wxCommandEvent& e);
336 void update(bool light);
337 private:
338 emulator_instance& inst;
339 wxWindow* pwin;
340 void insert_pass(int id, const std::string& label);
341 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
342 wxMenu* lookup_menu(const std::string& key);
343 wxMenuItem* sep;
344 std::map<int, unsigned> action_by_id;
345 std::map<unsigned, wxMenuItem*> item_by_action;
346 std::map<wxMenuItem*, wxMenu*> menu_by_item;
347 std::map<std::string, wxMenu*> submenu_by_name;
348 std::map<std::string, wxMenuItem*> submenui_by_name;
349 std::set<unsigned> toggles;
350 int next_id;
353 wxMenu* system_menu::lookup_menu(const std::string& key)
355 CHECK_UI_THREAD;
356 if(key == "")
357 return this;
358 if(submenu_by_name.count(key))
359 return submenu_by_name[key];
360 //Not found, create.
361 if(!sep)
362 sep = AppendSeparator();
363 auto p = lsplit(key);
364 wxMenu* into = lookup_menu(p.first);
365 submenu_by_name[key] = new wxMenu();
366 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
367 menu_by_item[submenui_by_name[key]] = into;
368 return submenu_by_name[key];
371 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
373 CHECK_UI_THREAD;
374 if(!sep)
375 sep = AppendSeparator();
377 auto p = lsplit(label);
378 wxMenu* into = lookup_menu(p.first);
380 action_by_id[next_id] = id;
381 std::string use_label = p.second + (dots ? "..." : "");
382 if(check) {
383 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
384 toggles.insert(id);
385 } else
386 item_by_action[id] = into->Append(next_id, towxstring(use_label));
387 menu_by_item[item_by_action[id]] = into;
388 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
389 wxCommandEventHandler(system_menu::on_select), NULL, this);
392 void system_menu::insert_pass(int id, const std::string& label)
394 CHECK_UI_THREAD;
395 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
396 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
397 Append(id, towxstring(label));
400 system_menu::system_menu(wxWindow* win, emulator_instance& _inst)
401 : inst(_inst)
403 CHECK_UI_THREAD;
404 pwin = win;
405 insert_pass(wxID_PAUSE, "Pause/Unpause");
406 insert_pass(wxID_FRAMEADVANCE, "Step frame");
407 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
408 insert_pass(wxID_NEXTPOLL, "Step poll");
409 sep = NULL;
412 system_menu::~system_menu()
416 void system_menu::on_select(wxCommandEvent& e)
418 CHECK_UI_THREAD;
419 if(!action_by_id.count(e.GetId()))
420 return;
421 unsigned act_id = action_by_id[e.GetId()];
422 const interface_action* act = NULL;
423 for(auto i : inst.rom->get_actions())
424 if(i->id == act_id) {
425 act = i;
426 break;
428 if(!act)
429 return;
430 try {
431 auto p = prompt_action_params(pwin, act->get_title(), act->params);
432 inst.iqueue->run([this, act_id,p]() { this->inst.rom->execute_action(act_id, p); });
433 } catch(canceled_exception& e) {
434 } catch(std::bad_alloc& e) {
435 OOM_panic();
439 void system_menu::update(bool light)
441 CHECK_UI_THREAD;
442 if(!light) {
443 next_id = wxID_ACTIONS_FIRST;
444 if(sep) {
445 Destroy(sep);
446 sep = NULL;
448 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
449 menu_by_item[i->second]->Destroy(i->second);
450 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
451 menu_by_item[i->second]->Destroy(i->second);
452 action_by_id.clear();
453 item_by_action.clear();
454 menu_by_item.clear();
455 submenu_by_name.clear();
456 submenui_by_name.clear();
457 toggles.clear();
459 for(auto i : inst.rom->get_actions())
460 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
462 for(auto i : item_by_action)
463 i.second->Enable(inst.rom->action_flags(i.first) & 1);
464 for(auto i : toggles)
465 item_by_action[i]->Check(inst.rom->action_flags(i) & 2);
468 std::string munge_name(const std::string& orig)
470 std::string newname;
471 regex_results r;
472 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
473 uint64_t sequence;
474 try {
475 sequence = parse_value<uint64_t>(r[2]);
476 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
477 } catch(...) {
478 newname = newname + "(2)";
480 } else {
481 newname = newname + "(2)";
483 return newname;
486 void handle_watch_load(emulator_instance& inst, std::map<std::string, std::string>& new_watches,
487 std::set<std::string>& old_watches)
489 auto proj = inst.project->get();
490 if(proj) {
491 for(auto i : new_watches) {
492 std::string name = i.first;
493 while(true) {
494 if(!old_watches.count(name)) {
495 try {
496 if(name != "" && i.second != "")
497 inst.mwatch->set(name, i.second);
498 } catch(std::exception& e) {
499 messages << "Can't set memory watch '" << name << "': "
500 << e.what() << std::endl;
502 break;
503 } else if(inst.mwatch->get_string(name) == i.second)
504 break;
505 else
506 name = munge_name(name);
509 } else {
510 for(auto i : new_watches)
511 try {
512 if(i.first != "" && i.second != "")
513 inst.mwatch->set(i.first, i.second);
514 } catch(std::exception& e) {
515 messages << "Can't set memory watch '" << i.first << "': "
516 << e.what() << std::endl;
518 for(auto i : old_watches)
519 if(!new_watches.count(i))
520 try {
521 inst.mwatch->clear(i);
522 } catch(std::exception& e) {
523 messages << "Can't clear memory watch '" << i << "': "
524 << e.what() << std::endl;
529 std::string get_default_screenshot_name(emulator_instance& inst)
531 auto p = inst.project->get();
532 if(!p)
533 return "";
534 else {
535 auto files = directory::enumerate(p->directory, ".*-[0-9]+\\.png");
536 std::set<std::string> numbers;
537 for(auto i : files) {
538 size_t split;
539 #ifdef FUCKED_SYSTEM
540 split = i.find_last_of("\\/");
541 #else
542 split = i.find_last_of("/");
543 #endif
544 std::string name = i;
545 if(split < name.length())
546 name = name.substr(split + 1);
547 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
548 if(r[1] != p->prefix)
549 continue;
550 numbers.insert(r[2]);
552 for(uint64_t i = 1;; i++) {
553 std::string candidate = (stringfmt() << i).str();
554 if(!numbers.count(candidate))
555 return p->prefix + "-" + candidate + ".png";
560 std::string project_prefixname(emulator_instance& inst, const std::string ext)
562 auto p = inst.project->get();
563 if(!p)
564 return "";
565 else
566 return p->prefix + "." + ext;
570 void recent_rom_selected(emulator_instance& inst, const recentfiles::multirom& file)
572 romload_request req;
573 req.packfile = file.packfile;
574 req.singlefile = file.singlefile;
575 req.core = file.core;
576 req.system = file.system;
577 req.region = file.region;
578 for(unsigned i = 0; i < file.files.size() && i < ROM_SLOT_COUNT; i++)
579 req.files[i] = file.files[i];
580 inst.iqueue->run_async([req]() {
581 CORE().command->invoke("unpause-emulator");
582 load_new_rom(req);
583 }, [](std::exception& e) {});
586 void recent_movie_selected(emulator_instance& inst, const recentfiles::path& file)
588 inst.iqueue->queue(CLOADSAVE::ldsm.name, file.get_path());
591 void recent_script_selected(emulator_instance& inst, const recentfiles::path& file)
593 inst.iqueue->queue(CLUA::run.name, file.get_path());
596 wxString getname(emulator_instance& inst)
598 std::string windowname = "lsnes rr" + lsnes_version + " [";
599 auto p = inst.project->get();
600 if(p)
601 windowname = windowname + p->name;
602 else
603 windowname = windowname + inst.rom->get_core_identifier();
604 windowname = windowname + "]";
605 return towxstring(windowname);
608 struct emu_args
610 emulator_instance* inst;
611 struct loaded_rom rom;
612 struct moviefile* initial;
613 bool load_has_to_succeed;
616 void* emulator_main(void* _args)
618 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
619 auto& inst = *args->inst;
620 try {
621 *inst.rom = args->rom;
622 messages << "Using core: " << inst.rom->get_core_identifier() << std::endl;
623 struct moviefile* movie = args->initial;
624 bool has_to_succeed = args->load_has_to_succeed;
625 platform::flush_command_queue();
626 main_loop(*inst.rom, *movie, has_to_succeed);
627 signal_program_exit();
628 } catch(std::bad_alloc& e) {
629 OOM_panic();
630 } catch(std::exception& e) {
631 messages << "FATAL: " << e.what() << std::endl;
632 platform::fatal_error();
634 delete args;
635 return NULL;
638 void join_emulator_thread()
640 emulation_thread->join();
643 bool is_readonly_mode(emulator_instance& inst)
645 bool ret;
646 inst.iqueue->run([&ret]() {
647 ret = *CORE().mlogic ? CORE().mlogic->get_movie().readonly_mode() : false;
649 return ret;
652 void set_speed(emulator_instance& inst, double target)
654 if(target < 0)
655 inst.framerate->set_speed_multiplier(std::numeric_limits<double>::infinity());
656 else
657 inst.framerate->set_speed_multiplier(target / 100);
660 void update_preferences()
662 preferred_core.clear();
663 for(auto i : core_type::get_core_types()) {
664 std::string val = i->get_hname() + " / " + i->get_core_identifier();
665 for(auto j : i->get_extensions()) {
666 std::string key = "ext:" + j;
667 if(core_selections.count(key) && core_selections[key] == val)
668 preferred_core[key] = i;
670 std::string key2 = "type:" + i->get_iname();
671 if(core_selections.count(key2) && core_selections[key2] == val)
672 preferred_core[key2] = i;
676 bool is_lsnes_movie(const std::string& filename)
678 std::istream* s = NULL;
679 try {
680 bool ans = false;
681 s = &zip::openrel(filename, "");
682 char buf[6] = {0};
683 s->read(buf, 5);
684 if(*s && !strcmp(buf, "lsmv\x1A"))
685 ans = true;
686 delete s;
687 if(ans) return true;
688 } catch(...) {
689 delete s;
691 try {
692 zip::reader r(filename);
693 std::istream& s = r["systemid"];
694 std::string s2;
695 std::getline(s, s2);
696 delete &s;
697 istrip_CR(s2);
698 return (s2 == "lsnes-rr1");
699 } catch(...) {
700 return false;
704 class loadfile : public wxFileDropTarget
706 public:
707 loadfile(wxwin_mainwindow* win, emulator_instance& _inst) : inst(_inst), pwin(win) {};
708 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
710 CHECK_UI_THREAD;
711 bool ret = false;
712 if(filenames.Count() == 2) {
713 std::string a = tostdstring(filenames[0]);
714 std::string b = tostdstring(filenames[1]);
715 bool amov = is_lsnes_movie(a);
716 bool bmov = is_lsnes_movie(b);
717 if(amov == bmov)
718 return false;
719 if(amov) std::swap(a, b);
720 inst.iqueue->run_async([a, b]() {
721 CORE().command->invoke("unpause-emulator");
722 romload_request req;
723 req.packfile = a;
724 load_new_rom(req);
725 CORE().command->invoke(CLOADSAVE::ldsm.name, b);
726 }, [](std::exception& e) {});
727 ret = true;
729 if(filenames.Count() == 1) {
730 std::string a = tostdstring(filenames[0]);
731 bool amov = is_lsnes_movie(a);
732 if(amov) {
733 inst.iqueue->queue(CLOADSAVE::ldsm.name, a);
734 pwin->recent_movies->add(a);
735 ret = true;
736 } else {
737 romload_request req;
738 req.packfile = a;
739 inst.iqueue->run_async([req]() {
740 CORE().command->invoke("unpause-emulator");
741 load_new_rom(req);
742 }, [](std::exception& e) {});
743 pwin->recent_roms->add(loadreq_to_multirom(req));
744 ret = true;
747 return ret;
749 emulator_instance& inst;
750 wxwin_mainwindow* pwin;
754 void boot_emulator(emulator_instance& inst, loaded_rom& rom, moviefile& movie, bool fscreen)
756 CHECK_UI_THREAD;
757 update_preferences();
758 try {
759 struct emu_args* a = new emu_args;
760 a->rom = rom;
761 a->initial = &movie;
762 a->load_has_to_succeed = false;
763 a->inst = &inst;
764 modal_pause_holder hld;
765 emulation_thread = new threads::thread(emulator_main, a);
766 main_window = new wxwin_mainwindow(inst, fscreen);
767 main_window->Show();
768 } catch(std::bad_alloc& e) {
769 OOM_panic();
773 wxwin_mainwindow::panel::panel(wxWindow* win, emulator_instance& _inst)
774 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS), inst(_inst)
776 CHECK_UI_THREAD;
777 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
778 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
779 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
780 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
781 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
782 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
783 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
784 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
785 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
786 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
787 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
788 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
789 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
790 SetMinSize(wxSize(512, 448));
793 void wxwin_mainwindow::menu_start(wxString name)
795 CHECK_UI_THREAD;
796 while(!upper.empty())
797 upper.pop();
798 current_menu = new wxMenu();
799 menubar->Append(current_menu, name);
802 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
804 CHECK_UI_THREAD;
805 while(!upper.empty())
806 upper.pop();
807 menubar->Append(menu, name);
808 current_menu = NULL;
811 wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
813 CHECK_UI_THREAD;
814 return current_menu->AppendSubMenu(menu, name);
817 void wxwin_mainwindow::menu_entry(int id, wxString name)
819 CHECK_UI_THREAD;
820 current_menu->Append(id, name);
821 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
822 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
825 void wxwin_mainwindow::menu_entry_check(int id, wxString name)
827 CHECK_UI_THREAD;
828 checkitems[id] = current_menu->AppendCheckItem(id, name);
829 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
830 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
833 void wxwin_mainwindow::menu_start_sub(wxString name)
835 CHECK_UI_THREAD;
836 wxMenu* old = current_menu;
837 upper.push(current_menu);
838 current_menu = new wxMenu();
839 old->AppendSubMenu(current_menu, name);
842 void wxwin_mainwindow::menu_end_sub()
844 current_menu = upper.top();
845 upper.pop();
848 bool wxwin_mainwindow::menu_ischecked(int id)
850 CHECK_UI_THREAD;
851 if(checkitems.count(id))
852 return checkitems[id]->IsChecked();
853 else
854 return false;
857 void wxwin_mainwindow::menu_check(int id, bool newstate)
859 CHECK_UI_THREAD;
860 if(checkitems.count(id))
861 return checkitems[id]->Check(newstate);
862 else
863 return;
866 void wxwin_mainwindow::menu_enable(int id, bool newstate)
868 CHECK_UI_THREAD;
869 auto item = menubar->FindItem(id);
870 if(!item)
871 return;
872 item->Enable(newstate);
875 void wxwin_mainwindow::menu_separator()
877 CHECK_UI_THREAD;
878 current_menu->AppendSeparator();
881 void wxwin_mainwindow::panel::request_paint()
883 CHECK_UI_THREAD;
884 Refresh();
887 std::pair<double, double> calc_scale_factors(double factor, bool ar, double par)
889 if(!ar)
890 return std::make_pair(factor, factor);
891 else if(par < 1) {
892 //Too wide, make taller.
893 return std::make_pair(factor, factor / par);
894 } else {
895 //Too narrow, make wider.
896 return std::make_pair(factor * par, factor);
900 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
902 CHECK_UI_THREAD;
903 if(wx_escape_count >= 3 && is_fs) {
904 //Leave fullscreen mode.
905 main_window->enter_or_leave_fullscreen(false);
907 inst.fbuf->render_framebuffer();
908 uint8_t* srcp[1];
909 int srcs[1];
910 uint8_t* dstp[1];
911 int dsts[1];
912 wxPaintDC dc(this);
913 uint32_t tw, th;
914 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
915 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled, inst.rom->get_PAR());
916 if(rotate_enabled) {
917 tw = inst.fbuf->main_screen.get_height() * sfactors.second + 0.5;
918 th = inst.fbuf->main_screen.get_width() * sfactors.first + 0.5;
919 } else {
920 tw = inst.fbuf->main_screen.get_width() * sfactors.first + 0.5;
921 th = inst.fbuf->main_screen.get_height() * sfactors.second + 0.5;
923 if(!tw || !th) {
924 main_window_dirty = false;
925 return;
927 //Scale this to fullscreen.
928 unsigned dx = 0, dy = 0;
929 if(is_fs) {
930 wxSize screen = main_window->GetSize();
932 double fss = min(1.0 * screen.GetWidth() / tw, 1.0 * screen.GetHeight() / th);
933 tw *= fss;
934 th *= fss;
935 if((signed)tw < screen.GetWidth())
936 dx = (screen.GetWidth() - tw) / 2;
937 if((signed)th < screen.GetHeight())
938 dy = (screen.GetHeight() - th) / 2;
939 if(/*becoming_fullscreen && */current_resolution != screen) {
940 //Force panel to fullscreen.
941 SetSize(screen);
942 Move(0, 0);
943 current_resolution = screen;
944 //becoming_fullscreen = false;
946 //Erase borders.
947 signed dx2 = dx + tw;
948 signed dy2 = dy + th;
949 dc.SetBrush(*wxBLACK_BRUSH);
950 dc.SetPen(*wxBLACK_PEN);
951 //Erase the borders we don't draw.
952 if(dx > 0) dc.DrawRectangle(0, 0, dx, screen.GetHeight());
953 if(dy > 0) dc.DrawRectangle(0, 0, screen.GetWidth(), dy);
954 if(dx2 < screen.GetWidth()) dc.DrawRectangle(dx2, 0, screen.GetWidth() - dx2, screen.GetHeight());
955 if(dy2 < screen.GetHeight()) dc.DrawRectangle(0, dy2, screen.GetWidth(), screen.GetHeight() - dy2);
958 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
959 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
960 if(screen_buffer) {
961 delete[] screen_buffer;
962 screen_buffer = NULL;
964 if(rotate_buffer) {
965 delete[] rotate_buffer;
966 rotate_buffer = NULL;
968 old_height = th;
969 old_width = tw;
970 old_flags = scaling_flags;
971 old_hflip = hflip_enabled;
972 old_vflip = vflip_enabled;
973 old_rotate = rotate_enabled;
974 uint32_t w = inst.fbuf->main_screen.get_width();
975 uint32_t h = inst.fbuf->main_screen.get_height();
976 if(w && h)
977 sws_ctx = sws_getCachedContext(sws_ctx, rotate_enabled ? h : w, rotate_enabled ? w : h,
978 AV_PIX_FMT_RGBA, tw, th, AV_PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
979 tw = max(tw, static_cast<uint32_t>(128));
980 th = max(th, static_cast<uint32_t>(112));
981 screen_buffer = new unsigned char[tw * th * 3 + 64];
982 if(aux)
983 rotate_buffer = new uint32_t[inst.fbuf->main_screen.get_width() *
984 inst.fbuf->main_screen.get_height()];
985 if(!is_fs) {
986 //This is not preformed in fullscreen mode.
987 SetMinSize(wxSize(tw, th));
988 signal_resize_needed();
991 if(aux) {
992 //Hflip, Vflip or rotate active.
993 size_t width = inst.fbuf->main_screen.get_width();
994 size_t height = inst.fbuf->main_screen.get_height();
995 size_t width1 = width - 1;
996 size_t height1 = height - 1;
997 size_t stride = inst.fbuf->main_screen.rowptr(1) - inst.fbuf->main_screen.rowptr(0);
998 uint32_t* pixels = inst.fbuf->main_screen.rowptr(0);
999 if(rotate_enabled) {
1000 for(unsigned y = 0; y < height; y++) {
1001 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
1002 uint32_t* dpixels = rotate_buffer + (height1 - y);
1003 if(hflip_enabled)
1004 for(unsigned x = 0; x < width; x++)
1005 dpixels[x * height] = pixels2[width1 - x];
1006 else
1007 for(unsigned x = 0; x < width; x++)
1008 dpixels[x * height] = pixels2[x];
1010 } else {
1011 for(unsigned y = 0; y < height; y++) {
1012 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
1013 uint32_t* dpixels = rotate_buffer + y * width;
1014 if(hflip_enabled)
1015 for(unsigned x = 0; x < width; x++)
1016 dpixels[x] = pixels2[width1 - x];
1017 else
1018 for(unsigned x = 0; x < width; x++)
1019 dpixels[x] = pixels2[x];
1023 if(aux)
1024 srcs[0] = 4 * (rotate_enabled ? inst.fbuf->main_screen.get_height() :
1025 inst.fbuf->main_screen.get_width());
1026 else
1027 srcs[0] = 4 * inst.fbuf->main_screen.get_stride();
1028 dsts[0] = 3 * tw;
1029 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : inst.fbuf->main_screen.rowptr(0));
1030 dstp[0] = screen_buffer;
1031 memset(screen_buffer, 0, tw * th * 3);
1032 if(inst.fbuf->main_screen.get_width() && inst.fbuf->main_screen.get_height())
1033 sws_scale(sws_ctx, srcp, srcs, 0, rotate_enabled ? inst.fbuf->main_screen.get_width() :
1034 inst.fbuf->main_screen.get_height(),
1035 dstp, dsts);
1036 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
1037 dc.DrawBitmap(bmp, dx, dy, false);
1038 main_window_dirty = false;
1039 main_window->update_statusbar();
1042 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
1044 //Blank.
1047 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
1049 CHECK_UI_THREAD;
1050 handle_wx_keyboard(inst, e, true);
1053 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
1055 CHECK_UI_THREAD;
1056 handle_wx_keyboard(inst, e, false);
1057 if(wx_escape_count >= 3 && is_fs) {
1058 //Force leave fullscreen mode.
1059 main_window->enter_or_leave_fullscreen(false);
1063 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
1065 CHECK_UI_THREAD;
1066 handle_wx_mouse(inst, e);
1069 wxwin_mainwindow::wxwin_mainwindow(emulator_instance& _inst, bool fscreen)
1070 : wxFrame(NULL, wxID_ANY, getname(_inst), wxDefaultPosition, wxSize(-1, -1),
1071 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX), inst(_inst)
1073 CHECK_UI_THREAD;
1074 download_in_progress = NULL;
1075 Centre();
1076 mwindow = NULL;
1077 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
1078 toplevel->Add(gpanel = new panel(this, inst), 1, wxGROW);
1079 toplevel->Add(spanel = new wxwin_status::panel(this, inst, gpanel, 20), 1, wxGROW);
1080 spanel_shown = true;
1081 toplevel->SetSizeHints(this);
1082 SetSizer(toplevel);
1083 Fit();
1084 gpanel->SetFocus();
1085 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
1086 SetMenuBar(menubar = new wxMenuBar);
1087 SetStatusBar(statusbar = new wxStatusBar(this));
1089 menu_start(wxT("File"));
1090 menu_start_sub(wxT("New"));
1091 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
1092 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
1093 menu_end_sub();
1094 menu_start_sub(wxT("Load"));
1095 menu_entry(wxID_LOAD_STATE, wxT("State..."));
1096 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
1097 menu_entry(wxID_DOWNLOAD, wxT("Download movie..."));
1098 if(loadlib::library::name() != "") {
1099 menu_separator();
1100 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name()));
1101 menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager"));
1103 menu_separator();
1104 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
1105 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST, wxT("ROM..."));
1106 menu_special_sub(wxT("Multifile ROM"), loadroms = new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST + 1,
1107 wxID_LOAD_ROM_IMAGE_LAST, [this](core_type* t) { this->do_load_rom_image(t); }));
1108 menu_special_sub(wxT("Project"), projects = new projects_menu(this, inst, wxID_PROJECT_FIRST,
1109 wxID_PROJECT_LAST, get_config_path() + "/recent-projects.txt", [this](const std::string& id) {
1110 this->project_selected(id); }));
1111 menu_separator();
1112 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu<recentfiles::multirom>(this, inst,
1113 wxID_RROM_FIRST, wxID_RROM_LAST, get_config_path() + "/recent-roms.txt", recent_rom_selected));
1114 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu<recentfiles::path>(this, inst,
1115 wxID_RMOVIE_FIRST, wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt",
1116 recent_movie_selected));
1117 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts = new recent_menu<recentfiles::path>(this, inst,
1118 wxID_RLUA_FIRST, wxID_RLUA_LAST, get_config_path() + "/recent-scripts.txt",
1119 recent_script_selected));
1120 menu_separator();
1121 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
1122 menu_separator();
1123 branches_menu* brlist;
1124 auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, inst,
1125 wxID_BRANCH_FIRST, wxID_BRANCH_LAST));
1126 brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
1127 brlist->update();
1128 menu_end_sub();
1129 menu_start_sub(wxT("Save"));
1130 menu_entry(wxID_SAVE_STATE, wxT("State..."));
1131 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
1132 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
1133 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
1134 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
1135 menu_separator();
1136 menu_entry(wxID_CHDIR, wxT("Change working directory..."));
1137 menu_separator();
1138 menu_special_sub(wxT("Upload"), new upload_menu(this, inst, wxID_UPLOAD_FIRST, wxID_UPLOAD_LAST));
1139 menu_end_sub();
1140 menu_start_sub(wxT("Close"));
1141 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
1142 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
1143 menu_enable(wxID_CLOSE_PROJECT, inst.project->get() != NULL);
1144 menu_enable(wxID_CLOSE_ROM, inst.project->get() == NULL);
1145 menu_end_sub();
1146 menu_separator();
1147 menu_entry(wxID_EXIT, wxT("Quit"));
1149 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this, inst)));
1151 menu_start(wxT("Movie"));
1152 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
1153 menu_check(wxID_READONLY_MODE, is_readonly_mode(inst));
1154 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
1155 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
1156 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
1157 menu_separator();
1158 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
1160 menu_start(wxT("Speed"));
1161 menu_entry(wxID_SPEED_5, wxT("1/20x"));
1162 menu_entry(wxID_SPEED_10, wxT("1/10x"));
1163 menu_entry(wxID_SPEED_17, wxT("1/6x"));
1164 menu_entry(wxID_SPEED_20, wxT("1/5x"));
1165 menu_entry(wxID_SPEED_25, wxT("1/4x"));
1166 menu_entry(wxID_SPEED_33, wxT("1/3x"));
1167 menu_entry(wxID_SPEED_50, wxT("1/2x"));
1168 menu_entry(wxID_SPEED_100, wxT("1x"));
1169 menu_entry(wxID_SPEED_150, wxT("1.5x"));
1170 menu_entry(wxID_SPEED_200, wxT("2x"));
1171 menu_entry(wxID_SPEED_300, wxT("3x"));
1172 menu_entry(wxID_SPEED_500, wxT("5x"));
1173 menu_entry(wxID_SPEED_1000, wxT("10x"));
1174 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
1175 menu_entry(wxID_SET_SPEED, wxT("Set..."));
1177 menu_start(wxT("Tools"));
1178 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
1179 menu_separator();
1180 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
1181 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
1182 menu_separator();
1183 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
1184 menu_separator();
1185 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
1186 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
1187 menu_entry(wxID_MULTITRACK, wxT("Multitrack..."));
1188 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
1189 menu_separator();
1190 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
1191 menu_separator();
1192 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
1193 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
1194 menu_separator();
1195 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
1196 menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
1197 tracelog_menu* trlog;
1198 auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, inst,
1199 wxID_TRACELOG_FIRST, wxID_TRACELOG_LAST));
1200 trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
1201 trlog->update();
1202 menu_entry(wxID_DISASSEMBLER, wxT("Disassembler..."));
1203 menu_separator();
1204 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
1205 menu_separator();
1206 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
1207 inst, wxID_DUMP_FIRST, wxID_DUMP_LAST)));
1209 menu_start(wxT("Configure"));
1210 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
1211 menu_check(wxID_SHOW_STATUS, true);
1212 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
1213 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
1214 menu_special_sub(wxT("Settings"), new settings_menu(this, inst, wxID_SETTINGS_FIRST));
1215 if(audioapi_driver_initialized()) {
1216 menu_separator();
1217 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
1218 menu_entry(wxID_VUDISPLAY, wxT("VU display / sound controls"));
1219 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
1221 menu_separator();
1222 menu_entry(wxID_ENTER_FULLSCREEN, wxT("Enter fullscreen mode"));
1224 menu_start(wxT("Help"));
1225 menu_entry(wxID_ABOUT, wxT("About..."));
1227 corechange.set(inst.dispatch->core_change, []() { signal_core_change(); });
1228 titlechange.set(inst.dispatch->title_change, []() { signal_core_change(); });
1229 newcore.set(notify_new_core, []() { update_preferences(); });
1230 unmuted.set(inst.dispatch->sound_unmute, [this](bool unmute) {
1231 runuifun([this, unmute]() { this->menu_check(wxID_AUDIO_ENABLED, unmute); });
1233 modechange.set(inst.dispatch->mode_change, [this](bool readonly) {
1234 runuifun([this, readonly]() { this->menu_check(wxID_READONLY_MODE, readonly); });
1236 gpanel->SetDropTarget(new loadfile(this, inst));
1237 spanel->SetDropTarget(new loadfile(this, inst));
1238 set_hasher_callback(hash_callback);
1239 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1240 menubar->SetMenuLabel(1, towxstring(inst.rom->get_systemmenu_name()));
1241 focus_timer = new _focus_timer;
1242 status_timer = new _status_timer;
1243 if(fscreen) {
1244 wx_escape_count = 0;
1245 enter_or_leave_fullscreen(true);
1249 wxwin_mainwindow::~wxwin_mainwindow()
1251 CHECK_UI_THREAD;
1252 if(sws_ctx) sws_freeContext(sws_ctx);
1253 if(screen_buffer) delete[] screen_buffer;
1254 if(rotate_buffer) delete[] rotate_buffer;
1255 focus_timer->Stop();
1256 delete focus_timer;
1257 status_timer->Stop();
1258 delete status_timer;
1261 void wxwin_mainwindow::request_paint()
1263 CHECK_UI_THREAD;
1264 gpanel->Refresh();
1267 void wxwin_mainwindow::on_close(wxCloseEvent& e)
1269 CHECK_UI_THREAD;
1270 //Veto it for now, latter things will delete it.
1271 e.Veto();
1272 inst.iqueue->queue("quit-emulator");
1275 void wxwin_mainwindow::notify_update() throw()
1277 CHECK_UI_THREAD;
1278 if(!main_window_dirty) {
1279 main_window_dirty = true;
1280 gpanel->Refresh();
1284 void wxwin_mainwindow::notify_resized() throw()
1286 CHECK_UI_THREAD;
1287 toplevel->Layout();
1288 toplevel->SetSizeHints(this);
1289 Fit();
1292 void wxwin_mainwindow::notify_update_status() throw()
1294 CHECK_UI_THREAD;
1295 spanel->request_paint();
1296 if(mwindow)
1297 mwindow->notify_update();
1298 status_updated = true;
1301 void wxwin_mainwindow::notify_exit() throw()
1303 CHECK_UI_THREAD;
1304 wxwidgets_exiting = true;
1305 join_emulator_thread();
1306 Destroy();
1309 std::string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1311 if(!vars.count(key))
1312 return "";
1313 return utf8::to8(vars.find(key)->second);
1316 void wxwin_mainwindow::update_statusbar()
1318 CHECK_UI_THREAD;
1319 if(download_in_progress) {
1320 statusbar->SetStatusText(towxstring(download_in_progress->statusmsg()));
1321 return;
1323 if(hashing_in_progress) {
1324 //TODO: Display this as a dialog.
1325 std::ostringstream s;
1326 s << "Hashing ROMs, approximately " << ((hashing_left + 524288) >> 20) << " of "
1327 << ((hashing_total + 524288) >> 20) << "MB left...";
1328 statusbar->SetStatusText(towxstring(s.str()));
1329 return;
1331 auto& vars = inst.status->get_read();
1332 if(!vars.valid) {
1333 inst.status->put_read();
1334 return;
1336 try {
1337 std::ostringstream s;
1338 bool recording = (vars.mode == 'R');
1339 if(vars.movie_valid) {
1340 if(recording)
1341 s << "Frame: " << vars.curframe;
1342 else
1343 s << "Frame: " << vars.curframe << "/" << vars.length;
1344 s << " Lag: " << vars.lag;
1345 if(vars.subframe == _lsnes_status::subframe_savepoint)
1346 s << " Subframe: S";
1347 else if(vars.subframe == _lsnes_status::subframe_video)
1348 s << " Subframe: V";
1349 else
1350 s << " Subframe: " << vars.subframe;
1351 } else {
1352 s << "Frame: N/A Lag: N/A Subframe: N/A";
1354 if(vars.saveslot_valid) {
1355 s << " Slot: ";
1356 if(vars.branch_valid) s << utf8::to8(vars.branch) << "ā†’";
1357 s << vars.saveslot;
1358 s << " [" << utf8::to8(vars.slotinfo) << "]";
1360 s << " Speed: " << vars.speed << "% ";
1361 if(vars.pause == _lsnes_status::pause_break)
1362 s << " Breakpoint";
1363 else if(vars.pause == _lsnes_status::pause_normal)
1364 s << " Paused";
1365 if(vars.dumping)
1366 s << " Dumping";
1367 if(vars.mode == 'C')
1368 s << " Corrupt";
1369 else if(vars.mode == 'R')
1370 s << " Recording";
1371 else if(vars.mode == 'P')
1372 s << " Playback";
1373 else if(vars.mode == 'F')
1374 s << " Finished";
1375 else
1376 s << " Unknown";
1377 if(vars.mbranch_valid)
1378 s << " Branch: " << utf8::to8(vars.mbranch);
1379 std::string macros = utf8::to8(vars.macros);
1380 if(macros.length())
1381 s << " Macros: " << macros;
1383 statusbar->SetStatusText(towxstring(s.str()));
1384 } catch(std::exception& e) {
1386 inst.status->put_read();
1389 #define NEW_KEYBINDING "A new binding..."
1390 #define NEW_ALIAS "A new alias..."
1391 #define NEW_WATCH "A new watch..."
1393 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1395 CHECK_UI_THREAD;
1396 try {
1397 handle_menu_click_cancelable(e);
1398 } catch(canceled_exception& e) {
1399 //Ignore.
1400 } catch(std::bad_alloc& e) {
1401 OOM_panic();
1402 } catch(std::exception& e) {
1403 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1407 void wxwin_mainwindow::refresh_title() throw()
1409 CHECK_UI_THREAD;
1410 SetTitle(getname(inst));
1411 auto p = inst.project->get();
1412 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1413 for(int i = wxID_LOAD_ROM_IMAGE_FIRST; i <= wxID_LOAD_ROM_IMAGE_LAST; i++)
1414 menu_enable(i, !p);
1415 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1416 menu_enable(wxID_CLOSE_ROM, p == NULL);
1417 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1418 menubar->SetMenuLabel(1, towxstring(inst.rom->get_systemmenu_name()));
1421 namespace
1423 struct movie_or_savestate
1425 public:
1426 typedef std::pair<std::string,std::string> returntype;
1427 movie_or_savestate(emulator_instance& _inst, bool is_state)
1428 : inst(_inst)
1430 state = is_state;
1432 filedialog_input_params input(bool save) const
1434 filedialog_input_params p;
1435 std::string ext = state ? inst.project->savestate_ext() : "lsmv";
1436 std::string name = state ? "Savestates" : "Movies";
1437 if(save) {
1438 p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
1439 p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
1440 } else
1441 p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
1442 ext));
1443 if(!save && state) {
1444 p.types.push_back(filedialog_type_entry("Savestates [playback]", "*." + ext +
1445 ";*." + ext + ".backup", ext));
1446 p.types.push_back(filedialog_type_entry("Savestates [recording]", "*." + ext +
1447 ";*." + ext + ".backup", ext));
1448 p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
1449 ";*." + ext + ".backup", ext));
1450 p.types.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext +
1451 ";*." + ext + ".backup", ext));
1453 p.default_type = save ? (state ? save_dflt_binary(*inst.settings) :
1454 movie_dflt_binary(*inst.settings)) : 0;
1455 return p;
1457 std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
1459 std::string cmdmod;
1460 if(save)
1461 cmdmod = p.typechoice ? "-binary" : "-zip";
1462 else if(state)
1463 switch(p.typechoice) {
1464 case 0: cmdmod = ""; break;
1465 case 1: cmdmod = "-readonly"; break;
1466 case 2: cmdmod = "-state"; break;
1467 case 3: cmdmod = "-preserve"; break;
1468 case 4: cmdmod = "-allbranches"; break;
1470 return std::make_pair(cmdmod, p.path);
1472 private:
1473 emulator_instance& inst;
1474 bool state;
1476 struct movie_or_savestate filetype_movie(lsnes_instance, false);
1477 struct movie_or_savestate filetype_savestate(lsnes_instance, true);
1480 void wxwin_mainwindow::project_selected(const std::string& id)
1482 std::string filename, displayname;
1483 bool load_ok = false;
1484 inst.iqueue->run([id, &filename, &displayname, &load_ok]() -> void {
1485 try {
1486 auto& p = CORE().project->load(id); //Check.
1487 filename = p.filename;
1488 displayname = p.name;
1489 load_ok = true;
1490 delete &p;
1491 switch_projects(id);
1492 } catch(std::exception& e) {
1493 messages << "Failed to change project: " << e.what() << std::endl;
1496 if(load_ok) {
1497 recentfiles::namedobj obj;
1498 obj._id = id;
1499 obj._filename = filename;
1500 obj._display = displayname;
1501 projects->add(obj);
1505 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1507 CHECK_UI_THREAD;
1508 std::string filename;
1509 std::pair<std::string, std::string> filename2;
1510 bool s;
1511 switch(e.GetId()) {
1512 case wxID_FRAMEADVANCE:
1513 inst.iqueue->queue("+advance-frame");
1514 inst.iqueue->queue("-advance-frame");
1515 return;
1516 case wxID_SUBFRAMEADVANCE:
1517 inst.iqueue->queue("+advance-poll");
1518 inst.iqueue->queue("-advance-poll");
1519 return;
1520 case wxID_NEXTPOLL:
1521 inst.iqueue->queue("advance-skiplag");
1522 return;
1523 case wxID_PAUSE:
1524 inst.iqueue->queue("pause-emulator");
1525 return;
1526 case wxID_EXIT:
1527 inst.iqueue->queue("quit-emulator");
1528 return;
1529 case wxID_AUDIO_ENABLED:
1530 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1531 return;
1532 case wxID_CANCEL_SAVES:
1533 inst.iqueue->queue("cancel-saves");
1534 return;
1535 case wxID_LOAD_MOVIE:
1536 filename = choose_file_load(this, "Load Movie", UI_get_project_moviepath(inst),
1537 filetype_movie).second;
1538 recent_movies->add(filename);
1539 inst.iqueue->queue(CLOADSAVE::ldm.name, filename);
1540 return;
1541 case wxID_LOAD_STATE:
1542 filename2 = choose_file_load(this, "Load State", UI_get_project_moviepath(inst),
1543 filetype_savestate);
1544 recent_movies->add(filename2.second);
1545 inst.iqueue->queue("load" + filename2.first + " " + filename2.second);
1546 return;
1547 case wxID_REWIND_MOVIE:
1548 inst.iqueue->queue("rewind-movie");
1549 return;
1550 case wxID_SAVE_MOVIE:
1551 filename2 = choose_file_save(this, "Save Movie", UI_get_project_moviepath(inst), filetype_movie,
1552 project_prefixname(inst, "lsmv"));
1553 recent_movies->add(filename2.second);
1554 inst.iqueue->queue("save-movie" + filename2.first + " " + filename2.second);
1555 return;
1556 case wxID_SAVE_SUBTITLES:
1557 inst.iqueue->queue(CSUBTITLE::save.name, choose_file_save(this, "Save subtitles",
1558 UI_get_project_moviepath(inst), filetype_sub, project_prefixname(inst, "sub")));
1559 return;
1560 case wxID_SAVE_STATE:
1561 filename2 = choose_file_save(this, "Save State", UI_get_project_moviepath(inst),
1562 filetype_savestate);
1563 recent_movies->add(filename2.second);
1564 inst.iqueue->queue("save-state" + filename2.first + " " + filename2.second);
1565 return;
1566 case wxID_SAVE_SCREENSHOT:
1567 inst.iqueue->queue(CFRAMEBUF::ss.name, choose_file_save(this, "Save Screenshot",
1568 UI_get_project_moviepath(inst), filetype_png, get_default_screenshot_name(inst)));
1569 return;
1570 case wxID_RUN_SCRIPT:
1571 inst.iqueue->queue(CLUA::run.name, pick_file_member(this, "Select Script",
1572 UI_get_project_otherpath(inst)));
1573 return;
1574 case wxID_RUN_LUA: {
1575 std::string f = choose_file_load(this, "Select Lua Script", UI_get_project_otherpath(inst),
1576 filetype_lua_script);
1577 inst.iqueue->queue(CLUA::run.name, f);
1578 recent_scripts->add(f);
1579 return;
1581 case wxID_RESET_LUA:
1582 inst.iqueue->queue(CLUA::reset.name);
1583 return;
1584 case wxID_EVAL_LUA:
1585 inst.iqueue->queue(CLUA::eval.name, pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1586 return;
1587 case wxID_READONLY_MODE:
1588 s = menu_ischecked(wxID_READONLY_MODE);
1589 inst.iqueue->run([s]() {
1590 auto& core = CORE();
1591 if(!s)
1592 core.lua2->callback_movie_lost("readwrite");
1593 if(*core.mlogic) core.mlogic->get_movie().readonly_mode(s);
1594 core.dispatch->mode_change(s);
1595 if(!s)
1596 core.lua2->callback_do_readwrite();
1597 core.supdater->update();
1598 core.dispatch->status_update();
1600 return;
1601 case wxID_AUTOHOLD:
1602 wxeditor_autohold_display(this, inst);
1603 return;
1604 case wxID_EDIT_AUTHORS:
1605 wxeditor_authors_display(this, inst);
1606 return;
1607 case wxID_EDIT_MACROS:
1608 wxeditor_macro_display(this, inst);
1609 return;
1610 case wxID_EDIT_SUBTITLES:
1611 wxeditor_subtitles_display(this, inst);
1612 return;
1613 case wxID_EDIT_VSUBTITLES:
1614 show_wxeditor_voicesub(this, inst);
1615 return;
1616 case wxID_EDIT_MEMORYWATCH:
1617 wxeditor_memorywatches_display(this, inst);
1618 return;
1619 case wxID_SAVE_MEMORYWATCH: {
1620 modal_pause_holder hld;
1621 std::set<std::string> old_watches;
1622 inst.iqueue->run([&old_watches]() { old_watches = CORE().mwatch->enumerate(); });
1623 std::string filename = choose_file_save(this, "Save watches to file",
1624 UI_get_project_otherpath(inst), filetype_watch);
1625 std::ofstream out(filename.c_str());
1626 for(auto i : old_watches) {
1627 std::string val;
1628 inst.iqueue->run([i, &val]() {
1629 try {
1630 val = CORE().mwatch->get_string(i);
1631 } catch(std::exception& e) {
1632 messages << "Can't get value of watch '" << i << "': " << e.what()
1633 << std::endl;
1636 out << i << std::endl << val << std::endl;
1638 out.close();
1639 return;
1641 case wxID_LOAD_MEMORYWATCH: {
1642 modal_pause_holder hld;
1643 std::set<std::string> old_watches;
1644 inst.iqueue->run([&old_watches]() { old_watches = CORE().mwatch->enumerate(); });
1645 std::map<std::string, std::string> new_watches;
1646 std::string filename = choose_file_load(this, "Choose memory watch file",
1647 UI_get_project_otherpath(inst), filetype_watch);
1648 try {
1649 std::istream& in = zip::openrel(filename, "");
1650 while(in) {
1651 std::string wname;
1652 std::string wexpr;
1653 std::getline(in, wname);
1654 std::getline(in, wexpr);
1655 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1657 delete &in;
1658 } catch(std::exception& e) {
1659 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1660 wxICON_EXCLAMATION);
1661 return;
1664 inst.iqueue->run([this, &new_watches, &old_watches]() {
1665 handle_watch_load(this->inst, new_watches, old_watches);
1667 return;
1669 case wxID_MEMORY_SEARCH:
1670 wxwindow_memorysearch_display(inst);
1671 return;
1672 case wxID_TASINPUT:
1673 wxeditor_tasinput_display(this, inst);
1674 return;
1675 case wxID_ABOUT: {
1676 std::ostringstream str;
1677 str << "Version: lsnes rr" << lsnes_version << std::endl;
1678 str << "Revision: " << lsnes_git_revision << std::endl;
1679 for(auto i : core_core::all_cores())
1680 if(!i->is_hidden())
1681 str << "Core: " << i->get_core_identifier() << std::endl;
1682 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1683 return;
1685 case wxID_SHOW_STATUS: {
1686 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1687 if(newstate)
1688 spanel->Show();
1689 if(newstate && !spanel_shown)
1690 toplevel->Add(spanel, 1, wxGROW);
1691 else if(!newstate && spanel_shown)
1692 toplevel->Detach(spanel);
1693 if(!newstate)
1694 spanel->Hide();
1695 spanel_shown = newstate;
1696 toplevel->Layout();
1697 toplevel->SetSizeHints(this);
1698 Fit();
1699 return;
1701 case wxID_DEDICATED_MEMORY_WATCH: {
1702 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1703 if(newstate && !mwindow) {
1704 mwindow = new wxwin_status(-1, inst, "Memory Watch");
1705 spanel->set_watch_flag(1);
1706 mwindow->Show();
1707 } else if(!newstate && mwindow) {
1708 mwindow->Destroy();
1709 mwindow = NULL;
1710 spanel->set_watch_flag(0);
1712 return;
1714 case wxID_SET_SPEED: {
1715 std::string value = "infinite";
1716 double val = inst.framerate->get_speed_multiplier();
1717 if(!(val == std::numeric_limits<double>::infinity()))
1718 value = (stringfmt() << (100 * val)).str();
1719 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1720 try {
1721 if(value == "infinite")
1722 inst.framerate->set_speed_multiplier(
1723 std::numeric_limits<double>::infinity());
1724 else {
1725 double v = parse_value<double>(value) / 100;
1726 if(v <= 0.0001)
1727 throw 42;
1728 inst.framerate->set_speed_multiplier(v);
1730 } catch(...) {
1731 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1733 return;
1735 case wxID_SPEED_5:
1736 set_speed(inst, 5);
1737 break;
1738 case wxID_SPEED_10:
1739 set_speed(inst, 10);
1740 break;
1741 case wxID_SPEED_17:
1742 set_speed(inst, 16.66666666666);
1743 break;
1744 case wxID_SPEED_20:
1745 set_speed(inst, 20);
1746 break;
1747 case wxID_SPEED_25:
1748 set_speed(inst, 25);
1749 break;
1750 case wxID_SPEED_33:
1751 set_speed(inst, 33.3333333333333);
1752 break;
1753 case wxID_SPEED_50:
1754 set_speed(inst, 50);
1755 break;
1756 case wxID_SPEED_100:
1757 set_speed(inst, 100);
1758 break;
1759 case wxID_SPEED_150:
1760 set_speed(inst, 150);
1761 break;
1762 case wxID_SPEED_200:
1763 set_speed(inst, 200);
1764 break;
1765 case wxID_SPEED_300:
1766 set_speed(inst, 300);
1767 break;
1768 case wxID_SPEED_500:
1769 set_speed(inst, 500);
1770 break;
1771 case wxID_SPEED_1000:
1772 set_speed(inst, 1000);
1773 break;
1774 case wxID_SPEED_TURBO:
1775 set_speed(inst, -1);
1776 break;
1777 case wxID_LOAD_LIBRARY: {
1778 std::string name = std::string("load ") + loadlib::library::name();
1779 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name,
1780 UI_get_project_otherpath(inst), single_type(loadlib::library::extension(),
1781 loadlib::library::name())))));
1782 handle_post_loadlibrary();
1783 break;
1785 case wxID_PLUGIN_MANAGER:
1786 wxeditor_plugin_manager_display(this);
1787 return;
1788 case wxID_RELOAD_ROM_IMAGE:
1789 inst.iqueue->run([]() {
1790 CORE().command->invoke("unpause-emulator");
1791 reload_current_rom();
1793 return;
1794 case wxID_NEW_MOVIE:
1795 show_projectwindow(this, inst);
1796 return;
1797 case wxID_SHOW_MESSAGES:
1798 msg_window->reshow();
1799 return;
1800 case wxID_CONFLICTRESOLUTION:
1801 show_conflictwindow(this);
1802 return;
1803 case wxID_VUDISPLAY:
1804 open_vumeter_window(this, inst);
1805 return;
1806 case wxID_DISASSEMBLER:
1807 wxeditor_disassembler_display(this, inst);
1808 return;
1809 case wxID_MOVIE_EDIT:
1810 wxeditor_movie_display(this, inst);
1811 return;
1812 case wxID_NEW_PROJECT:
1813 open_new_project_window(this, inst);
1814 return;
1815 case wxID_CLOSE_PROJECT:
1816 inst.iqueue->run([this]() -> void { this->inst.project->set(NULL); });
1817 return;
1818 case wxID_CLOSE_ROM:
1819 inst.iqueue->run([]() -> void { close_rom(); });
1820 return;
1821 case wxID_ENTER_FULLSCREEN:
1822 wx_escape_count = 0;
1823 enter_or_leave_fullscreen(true);
1824 return;
1825 case wxID_LOAD_ROM_IMAGE_FIRST:
1826 do_load_rom_image(NULL);
1827 return;
1828 case wxID_HEXEDITOR:
1829 wxeditor_hexedit_display(this, inst);
1830 return;
1831 case wxID_MULTITRACK:
1832 wxeditor_multitrack_display(this, inst);
1833 return;
1834 case wxID_CHDIR: {
1835 wxDirDialog* d = new wxDirDialog(this, wxT("Change working directory"), wxT("."),
1836 wxDD_DIR_MUST_EXIST);
1837 if(d->ShowModal() == wxID_CANCEL) {
1838 d->Destroy();
1839 return;
1841 std::string path = tostdstring(d->GetPath());
1842 d->Destroy();
1843 chdir(path.c_str());
1844 messages << "Changed working directory to '" << path << "'" << std::endl;
1845 return;
1847 case wxID_DOWNLOAD: {
1848 if(download_in_progress) return;
1849 filename = pick_text(this, "Download movie", "Enter URL to download");
1850 download_in_progress = new file_download();
1851 download_in_progress->url = lsnes_uri_rewrite(filename);
1852 download_in_progress->target_slot = "wxwidgets_download_tmp";
1853 download_in_progress->do_async(*inst.rom);
1854 new download_timer(this, inst);
1855 return;
1860 void wxwin_mainwindow::action_updated()
1862 runuifun([this]() { reinterpret_cast<system_menu*>(sysmenu)->update(true); });
1865 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs)
1867 CHECK_UI_THREAD;
1868 if(fs && !is_fs) {
1869 //Save current resolution, so we can see the change.
1870 current_resolution = main_window->GetSize();
1871 if(spanel_shown)
1872 toplevel->Detach(spanel);
1873 spanel->Hide();
1874 is_fs = fs;
1875 ShowFullScreen(true);
1876 becoming_fullscreen = true;
1877 request_paint(); //Finish the resizing by running paint handler.
1878 } else if(!fs && is_fs) {
1879 becoming_fullscreen = false;
1880 ShowFullScreen(false);
1881 gpanel->Show();
1882 if(spanel_shown) {
1883 spanel->Show();
1884 toplevel->Add(spanel, 1, wxGROW);
1886 Fit();
1887 gpanel->SetFocus();
1888 is_fs = fs;
1889 wx_escape_count = 0;
1890 request_paint(); //Don't leave graphical corruption.
1894 namespace
1896 struct command::stub _exit_fullscreen = {"exit-fullscreen", "Exit fullscreen",
1897 "Syntax: exit_fullscreen\nExit fullscreen"};
1898 command::fnptr<> exit_fullscreen(lsnes_cmds, _exit_fullscreen,
1899 []() throw(std::bad_alloc, std::runtime_error) {
1900 runuifun([]() {
1901 if(is_fs)
1902 main_window->enter_or_leave_fullscreen(false);