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