Free some more memory before quitting
[lsnes.git] / src / platform / wxwidgets / mainwindow.cpp
blob9745a9ba15f1e424a420fda2b91b4f85a32dd0af
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 "core/audioapi.hpp"
18 #include "core/command.hpp"
19 #include "core/controller.hpp"
20 #include "core/controllerframe.hpp"
21 #include "core/dispatch.hpp"
22 #include "core/framebuffer.hpp"
23 #include "core/framerate.hpp"
24 #include "core/keymapper.hpp"
25 #include "interface/romtype.hpp"
26 #include "core/loadlib.hpp"
27 #include "lua/lua.hpp"
28 #include "core/mainloop.hpp"
29 #include "core/memorywatch.hpp"
30 #include "core/misc.hpp"
31 #include "core/moviedata.hpp"
32 #include "core/project.hpp"
33 #include "core/romloader.hpp"
34 #include "core/settings.hpp"
35 #include "core/window.hpp"
36 #include "library/directory.hpp"
37 #include "library/minmax.hpp"
38 #include "library/string.hpp"
39 #include "library/zip.hpp"
40 #if defined(_WIN32) || defined(_WIN64) || defined(TEST_WIN32_CODE)
41 #define FUCKED_SYSTEM
42 #endif
44 #include <cmath>
45 #include <vector>
46 #include <string>
49 extern "C"
51 #ifndef UINT64_C
52 #define UINT64_C(val) val##ULL
53 #endif
54 #include <libswscale/swscale.h>
57 enum
59 wxID_PAUSE = wxID_HIGHEST + 1,
60 wxID_FRAMEADVANCE,
61 wxID_SUBFRAMEADVANCE,
62 wxID_NEXTPOLL,
63 wxID_AUDIO_ENABLED,
64 wxID_SAVE_STATE,
65 wxID_SAVE_MOVIE,
66 wxID_SAVE_SUBTITLES,
67 wxID_LOAD_STATE,
68 wxID_LOAD_MOVIE,
69 wxID_RUN_SCRIPT,
70 wxID_RUN_LUA,
71 wxID_RESET_LUA,
72 wxID_EVAL_LUA,
73 wxID_SAVE_SCREENSHOT,
74 wxID_READONLY_MODE,
75 wxID_EDIT_AUTHORS,
76 wxID_AUTOHOLD,
77 wxID_EDIT_MEMORYWATCH,
78 wxID_SAVE_MEMORYWATCH,
79 wxID_LOAD_MEMORYWATCH,
80 wxID_EDIT_SUBTITLES,
81 wxID_EDIT_VSUBTITLES,
82 wxID_DUMP_FIRST,
83 wxID_DUMP_LAST = wxID_DUMP_FIRST + 1023,
84 wxID_REWIND_MOVIE,
85 wxID_MEMORY_SEARCH,
86 wxID_CANCEL_SAVES,
87 wxID_SHOW_STATUS,
88 wxID_SET_SPEED,
89 wxID_SPEED_5,
90 wxID_SPEED_10,
91 wxID_SPEED_17,
92 wxID_SPEED_20,
93 wxID_SPEED_25,
94 wxID_SPEED_33,
95 wxID_SPEED_50,
96 wxID_SPEED_100,
97 wxID_SPEED_150,
98 wxID_SPEED_200,
99 wxID_SPEED_300,
100 wxID_SPEED_500,
101 wxID_SPEED_1000,
102 wxID_SPEED_TURBO,
103 wxID_LOAD_LIBRARY,
104 wxID_RELOAD_ROM_IMAGE,
105 wxID_LOAD_ROM_IMAGE_FIRST,
106 wxID_LOAD_ROM_IMAGE_LAST = wxID_LOAD_ROM_IMAGE_FIRST + 1023,
107 wxID_NEW_MOVIE,
108 wxID_SHOW_MESSAGES,
109 wxID_DEDICATED_MEMORY_WATCH,
110 wxID_RMOVIE_FIRST,
111 wxID_RMOVIE_LAST = wxID_RMOVIE_FIRST + 16,
112 wxID_RROM_FIRST,
113 wxID_RROM_LAST = wxID_RROM_FIRST + 16,
114 wxID_CONFLICTRESOLUTION,
115 wxID_VUDISPLAY,
116 wxID_MOVIE_EDIT,
117 wxID_TASINPUT,
118 wxID_NEW_PROJECT,
119 wxID_CLOSE_PROJECT,
120 wxID_CLOSE_ROM,
121 wxID_EDIT_MACROS,
122 wxID_ENTER_FULLSCREEN,
123 wxID_ACTIONS_FIRST,
124 wxID_ACTIONS_LAST = wxID_ACTIONS_FIRST + 256,
125 wxID_SETTINGS_FIRST,
126 wxID_SETTINGS_LAST = wxID_SETTINGS_FIRST + 256,
127 wxID_HEXEDITOR,
128 wxID_MULTITRACK,
129 wxID_CHDIR,
130 wxID_RLUA_FIRST,
131 wxID_RLUA_LAST = wxID_RLUA_FIRST + 16,
132 wxID_UPLOAD_FIRST,
133 wxID_UPLOAD_LAST = wxID_UPLOAD_FIRST + 256,
134 wxID_DOWNLOAD,
135 wxID_TRACELOG_FIRST,
136 wxID_TRACELOG_LAST = wxID_TRACELOG_FIRST + 256,
137 wxID_PLUGIN_MANAGER,
138 wxID_BRANCH_FIRST,
139 wxID_BRANCH_LAST = wxID_BRANCH_FIRST + 10240,
140 wxID_PROJECT_FIRST,
141 wxID_PROJECT_LAST = wxID_PROJECT_FIRST + 17,
142 wxID_DISASSEMBLER,
146 double video_scale_factor = 1.0;
147 int scaling_flags = SWS_POINT;
148 bool arcorrect_enabled = false;
149 bool hflip_enabled = false;
150 bool vflip_enabled = false;
151 bool rotate_enabled = false;
153 namespace
155 std::string last_volume = "0dB";
156 std::string last_volume_record = "0dB";
157 std::string last_volume_voice = "0dB";
158 unsigned char* screen_buffer;
159 struct SwsContext* sws_ctx;
160 uint32_t* rotate_buffer;
161 uint32_t old_width;
162 uint32_t old_height;
163 int old_flags = SWS_POINT;
164 bool old_hflip = false;
165 bool old_vflip = false;
166 bool old_rotate = false;
167 bool main_window_dirty;
168 bool is_fs = false;
169 bool hashing_in_progress = false;
170 uint64_t hashing_left = 0;
171 uint64_t hashing_total = 0;
172 int64_t last_update = 0;
173 thread_class* emulation_thread;
175 settingvar::variable<settingvar::model_bool<settingvar::yes_no>> background_audio(lsnes_vset,
176 "background-audio", "GUIā€£Enable background audio", true);
178 class _focus_timer : public wxTimer
180 public:
181 _focus_timer()
183 was_focused = (wxWindow::FindFocus() != NULL);
184 was_enabled = platform::is_sound_enabled();
185 Start(500);
187 void Notify()
189 bool is_focused = (wxWindow::FindFocus() != NULL);
190 if(is_focused && !was_focused) {
191 //Gained focus.
192 if(!background_audio)
193 platform::sound_enable(was_enabled);
194 } else if(!is_focused && was_focused) {
195 //Lost focus.
196 was_enabled = platform::is_sound_enabled();
197 if(!background_audio)
198 platform::sound_enable(false);
200 was_focused = is_focused;
202 private:
203 bool was_focused;
204 bool was_enabled;
207 class download_timer : public wxTimer
209 public:
210 download_timer(wxwin_mainwindow* main)
212 w = main;
213 Start(50);
215 void Notify()
217 if(w->download_in_progress->finished) {
218 w->update_statusbar(std::map<std::string, std::u32string>());
219 auto old = w->download_in_progress;
220 w->download_in_progress = NULL;
221 if(old->errormsg != "") {
222 show_message_ok(w, "Error downloading movie", old->errormsg,
223 wxICON_EXCLAMATION);
224 } else {
225 platform::queue("load-movie $MEMORY:wxwidgets_download_tmp");
227 delete old;
228 Stop();
229 delete this;
230 } else {
231 w->update_statusbar(std::map<std::string, std::u32string>());
234 private:
235 wxwin_mainwindow* w;
238 void hash_callback(uint64_t left, uint64_t total)
240 wxwin_mainwindow* mwin = main_window;
241 if(left == 0xFFFFFFFFFFFFFFFFULL) {
242 hashing_in_progress = false;
243 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
244 last_update = get_utime() - 2000000;
245 return;
247 hashing_in_progress = true;
248 hashing_left = left;
249 hashing_total = total;
250 uint64_t this_update = get_utime();
251 if(this_update < last_update - 1000000 || this_update > last_update + 1000000) {
252 runuifun([mwin]() { if(mwin) mwin->notify_update_status(); });
253 last_update = this_update;
257 std::pair<std::string, std::string> lsplit(std::string l)
259 for(unsigned i = 0; i < l.length() - 3; i++)
260 if((uint8_t)l[i] == 0xE2 && (uint8_t)l[i + 1] == 0x80 && (uint8_t)l[i + 2] == 0xA3)
261 return std::make_pair(l.substr(0, i), l.substr(i + 3));
262 return std::make_pair("", l);
265 recentfiles::multirom loadreq_to_multirom(const romload_request& req)
267 recentfiles::multirom r;
268 r.packfile = req.packfile;
269 r.singlefile = req.singlefile;
270 r.core = req.core;
271 r.system = req.system;
272 r.region = req.region;
273 for(unsigned i = 0; i < ROM_SLOT_COUNT; i++)
274 if(req.files[i] != "") {
275 r.files.resize(i + 1);
276 r.files[i] = req.files[i];
278 return r;
281 class system_menu : public wxMenu
283 public:
284 system_menu(wxWindow* win);
285 ~system_menu();
286 void on_select(wxCommandEvent& e);
287 void update(bool light);
288 private:
289 wxWindow* pwin;
290 void insert_pass(int id, const std::string& label);
291 void insert_act(unsigned id, const std::string& label, bool dots, bool check);
292 wxMenu* lookup_menu(const std::string& key);
293 wxMenuItem* sep;
294 std::map<int, unsigned> action_by_id;
295 std::map<unsigned, wxMenuItem*> item_by_action;
296 std::map<wxMenuItem*, wxMenu*> menu_by_item;
297 std::map<std::string, wxMenu*> submenu_by_name;
298 std::map<std::string, wxMenuItem*> submenui_by_name;
299 std::set<unsigned> toggles;
300 int next_id;
303 wxMenu* system_menu::lookup_menu(const std::string& key)
305 if(key == "")
306 return this;
307 if(submenu_by_name.count(key))
308 return submenu_by_name[key];
309 //Not found, create.
310 if(!sep)
311 sep = AppendSeparator();
312 auto p = lsplit(key);
313 wxMenu* into = lookup_menu(p.first);
314 submenu_by_name[key] = new wxMenu();
315 submenui_by_name[key] = into->AppendSubMenu(submenu_by_name[key], towxstring(p.second));
316 menu_by_item[submenui_by_name[key]] = into;
317 return submenu_by_name[key];
320 void system_menu::insert_act(unsigned id, const std::string& label, bool dots, bool check)
322 if(!sep)
323 sep = AppendSeparator();
325 auto p = lsplit(label);
326 wxMenu* into = lookup_menu(p.first);
328 action_by_id[next_id] = id;
329 std::string use_label = p.second + (dots ? "..." : "");
330 if(check) {
331 item_by_action[id] = into->AppendCheckItem(next_id, towxstring(use_label));
332 toggles.insert(id);
333 } else
334 item_by_action[id] = into->Append(next_id, towxstring(use_label));
335 menu_by_item[item_by_action[id]] = into;
336 pwin->Connect(next_id++, wxEVT_COMMAND_MENU_SELECTED,
337 wxCommandEventHandler(system_menu::on_select), NULL, this);
340 void system_menu::insert_pass(int id, const std::string& label)
342 pwin->Connect(id, wxEVT_COMMAND_MENU_SELECTED,
343 wxCommandEventHandler(wxwin_mainwindow::handle_menu_click), NULL, pwin);
344 Append(id, towxstring(label));
347 system_menu::system_menu(wxWindow* win)
349 pwin = win;
350 insert_pass(wxID_PAUSE, "Pause/Unpause");
351 insert_pass(wxID_FRAMEADVANCE, "Step frame");
352 insert_pass(wxID_SUBFRAMEADVANCE, "Step subframe");
353 insert_pass(wxID_NEXTPOLL, "Step poll");
354 sep = NULL;
357 system_menu::~system_menu()
361 void system_menu::on_select(wxCommandEvent& e)
363 if(!action_by_id.count(e.GetId()))
364 return;
365 unsigned act_id = action_by_id[e.GetId()];
366 const interface_action* act = NULL;
367 for(auto i : our_rom.rtype->get_actions())
368 if(i->id == act_id) {
369 act = i;
370 break;
372 if(!act)
373 return;
374 try {
375 auto p = prompt_action_params(pwin, act->get_title(), act->params);
376 runemufn([act_id,p]() { our_rom.rtype->execute_action(act_id, p); });
377 } catch(canceled_exception& e) {
378 } catch(std::bad_alloc& e) {
379 OOM_panic();
383 void system_menu::update(bool light)
385 if(!light) {
386 next_id = wxID_ACTIONS_FIRST;
387 if(sep) {
388 Destroy(sep);
389 sep = NULL;
391 for(auto i = item_by_action.begin(); i != item_by_action.end(); i++)
392 menu_by_item[i->second]->Destroy(i->second);
393 for(auto i = submenui_by_name.rbegin(); i != submenui_by_name.rend(); i++)
394 menu_by_item[i->second]->Destroy(i->second);
395 action_by_id.clear();
396 item_by_action.clear();
397 menu_by_item.clear();
398 submenu_by_name.clear();
399 submenui_by_name.clear();
400 toggles.clear();
402 for(auto i : our_rom.rtype->get_actions())
403 insert_act(i->id, i->get_title(), !i->params.empty(), i->is_toggle());
405 for(auto i : item_by_action)
406 i.second->Enable(our_rom.rtype->action_flags(i.first) & 1);
407 for(auto i : toggles)
408 item_by_action[i]->Check(our_rom.rtype->action_flags(i) & 2);
411 std::string munge_name(const std::string& orig)
413 std::string newname;
414 regex_results r;
415 if(r = regex("(.*)\\(([0-9]+)\\)", newname)) {
416 uint64_t sequence;
417 try {
418 sequence = parse_value<uint64_t>(r[2]);
419 newname = (stringfmt() << r[1] << "(" << sequence + 1 << ")").str();
420 } catch(...) {
421 newname = newname + "(2)";
423 } else {
424 newname = newname + "(2)";
426 return newname;
429 void handle_watch_load(std::map<std::string, std::string>& new_watches, std::set<std::string>& old_watches)
431 auto proj = project_get();
432 if(proj) {
433 for(auto i : new_watches) {
434 std::string name = i.first;
435 while(true) {
436 if(!old_watches.count(name)) {
437 try {
438 if(name != "" && i.second != "")
439 lsnes_memorywatch.set(name, i.second);
440 } catch(std::exception& e) {
441 messages << "Can't set memory watch '" << name << "': "
442 << e.what() << std::endl;
444 break;
445 } else if(lsnes_memorywatch.get_string(name) == i.second)
446 break;
447 else
448 name = munge_name(name);
451 } else {
452 for(auto i : new_watches)
453 try {
454 if(i.first != "" && i.second != "")
455 lsnes_memorywatch.set(i.first, i.second);
456 } catch(std::exception& e) {
457 messages << "Can't set memory watch '" << i.first << "': "
458 << e.what() << std::endl;
460 for(auto i : old_watches)
461 if(!new_watches.count(i))
462 try {
463 lsnes_memorywatch.clear(i);
464 } catch(std::exception& e) {
465 messages << "Can't clear memory watch '" << i << "': "
466 << e.what() << std::endl;
471 std::string get_default_screenshot_name()
473 auto p = project_get();
474 if(!p)
475 return "";
476 else {
477 auto files = enumerate_directory(p->directory, ".*-[0-9]+\\.png");
478 std::set<std::string> numbers;
479 for(auto i : files) {
480 size_t split;
481 #ifdef FUCKED_SYSTEM
482 split = i.find_last_of("\\/");
483 #else
484 split = i.find_last_of("/");
485 #endif
486 std::string name = i;
487 if(split < name.length())
488 name = name.substr(split + 1);
489 regex_results r = regex("(.*)-([0-9]+)\\.png", name);
490 if(r[1] != p->prefix)
491 continue;
492 numbers.insert(r[2]);
494 for(uint64_t i = 1;; i++) {
495 std::string candidate = (stringfmt() << i).str();
496 if(!numbers.count(candidate))
497 return p->prefix + "-" + candidate + ".png";
502 std::string project_prefixname(const std::string ext)
504 auto p = project_get();
505 if(!p)
506 return "";
507 else
508 return p->prefix + "." + ext;
512 double pick_volume(wxWindow* win, const std::string& title, std::string& last)
514 std::string value;
515 regex_results r;
516 double parsed = 1;
517 value = pick_text(win, title, "Enter volume in absolute units, percentage (%) or dB:",
518 last);
519 if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)", value))
520 parsed = strtod(r[1].c_str(), NULL);
521 else if(r = regex("([0-9]*\\.[0-9]+|[0-9]+)%", value))
522 parsed = strtod(r[1].c_str(), NULL) / 100;
523 else if(r = regex("([+-]?([0-9]*.[0-9]+|[0-9]+))dB", value))
524 parsed = pow(10, strtod(r[1].c_str(), NULL) / 20);
525 else {
526 wxMessageBox(wxT("Invalid volume"), _T("Error"), wxICON_EXCLAMATION | wxOK, win);
527 return -1;
529 last = value;
530 return parsed;
533 void recent_rom_selected(const recentfiles::multirom& file)
535 romload_request req;
536 req.packfile = file.packfile;
537 req.singlefile = file.singlefile;
538 req.core = file.core;
539 req.system = file.system;
540 req.region = file.region;
541 for(unsigned i = 0; i < file.files.size() && i < ROM_SLOT_COUNT; i++)
542 req.files[i] = file.files[i];
543 runemufn_async([req]() {
544 lsnes_cmd.invoke("unpause-emulator");
545 load_new_rom(req);
549 void recent_movie_selected(const recentfiles::path& file)
551 platform::queue("load-smart " + file.get_path());
554 void recent_script_selected(const recentfiles::path& file)
556 platform::queue("run-lua " + file.get_path());
559 wxString getname()
561 std::string windowname = "lsnes rr" + lsnes_version + " [";
562 auto p = project_get();
563 if(p)
564 windowname = windowname + p->name;
565 else
566 windowname = windowname + our_rom.rtype->get_core_identifier();
567 windowname = windowname + "]";
568 return towxstring(windowname);
571 struct emu_args
573 struct loaded_rom rom;
574 struct moviefile* initial;
575 bool load_has_to_succeed;
578 void* emulator_main(void* _args)
580 struct emu_args* args = reinterpret_cast<struct emu_args*>(_args);
581 try {
582 our_rom = args->rom;
583 messages << "Using core: " << our_rom.rtype->get_core_identifier() << std::endl;
584 struct moviefile* movie = args->initial;
585 bool has_to_succeed = args->load_has_to_succeed;
586 platform::flush_command_queue();
587 main_loop(our_rom, *movie, has_to_succeed);
588 signal_program_exit();
589 } catch(std::bad_alloc& e) {
590 OOM_panic();
591 } catch(std::exception& e) {
592 messages << "FATAL: " << e.what() << std::endl;
593 platform::fatal_error();
595 delete args;
596 return NULL;
599 void join_emulator_thread()
601 emulation_thread->join();
604 keyboard::mouse_calibration mouse_cal = {0};
605 keyboard::key_mouse mouse_x(lsnes_kbd, "mouse_x", "mouse", mouse_cal);
606 keyboard::key_mouse mouse_y(lsnes_kbd, "mouse_y", "mouse", mouse_cal);
607 keyboard::key_key mouse_l(lsnes_kbd, "mouse_left", "mouse");
608 keyboard::key_key mouse_m(lsnes_kbd, "mouse_center", "mouse");
609 keyboard::key_key mouse_r(lsnes_kbd, "mouse_right", "mouse");
610 keyboard::key_key mouse_i(lsnes_kbd, "mouse_inwindow", "mouse");
612 std::pair<double, double> calc_scale_factors(double factor, bool ar,
613 double par)
615 if(!ar)
616 return std::make_pair(factor, factor);
617 else if(par < 1) {
618 //Too wide, make taller.
619 return std::make_pair(factor, factor / par);
620 } else {
621 //Too narrow, make wider.
622 return std::make_pair(factor * par, factor);
626 void handle_wx_mouse(wxMouseEvent& e)
628 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled,
629 (our_rom.rtype) ? our_rom.rtype->get_PAR() : 1.0);
630 platform::queue(keypress(keyboard::modifier_set(), mouse_x, e.GetX() / sfactors.first));
631 platform::queue(keypress(keyboard::modifier_set(), mouse_y, e.GetY() / sfactors.second));
632 if(e.Entering())
633 platform::queue(keypress(keyboard::modifier_set(), mouse_i, 1));
634 if(e.Leaving())
635 platform::queue(keypress(keyboard::modifier_set(), mouse_i, 0));
636 if(e.LeftDown())
637 platform::queue(keypress(keyboard::modifier_set(), mouse_l, 1));
638 if(e.LeftUp())
639 platform::queue(keypress(keyboard::modifier_set(), mouse_l, 0));
640 if(e.MiddleDown())
641 platform::queue(keypress(keyboard::modifier_set(), mouse_m, 1));
642 if(e.MiddleUp())
643 platform::queue(keypress(keyboard::modifier_set(), mouse_m, 0));
644 if(e.RightDown())
645 platform::queue(keypress(keyboard::modifier_set(), mouse_r, 1));
646 if(e.RightUp())
647 platform::queue(keypress(keyboard::modifier_set(), mouse_r, 0));
650 bool is_readonly_mode()
652 bool ret;
653 runemufn([&ret]() { ret = movb ? movb.get_movie().readonly_mode() : false; });
654 return ret;
657 std::pair<int, int> UI_controller_index_by_logical(unsigned lid)
659 std::pair<int, int> ret;
660 runemufn([&ret, lid]() { ret = controls.lcid_to_pcid(lid); });
661 return ret;
664 void set_speed(double target)
666 if(target < 0)
667 set_speed_multiplier(std::numeric_limits<double>::infinity());
668 else
669 set_speed_multiplier(target / 100);
672 void update_preferences()
674 preferred_core.clear();
675 for(auto i : core_type::get_core_types()) {
676 std::string val = i->get_hname() + " / " + i->get_core_identifier();
677 for(auto j : i->get_extensions()) {
678 std::string key = "ext:" + j;
679 if(core_selections.count(key) && core_selections[key] == val)
680 preferred_core[key] = i;
682 std::string key2 = "type:" + i->get_iname();
683 if(core_selections.count(key2) && core_selections[key2] == val)
684 preferred_core[key2] = i;
688 std::string movie_path()
690 return lsnes_vset["moviepath"].str();
693 bool is_lsnes_movie(const std::string& filename)
695 std::istream* s = NULL;
696 try {
697 bool ans = false;
698 s = &zip::openrel(filename, "");
699 char buf[6] = {0};
700 s->read(buf, 5);
701 if(*s && !strcmp(buf, "lsmv\x1A"))
702 ans = true;
703 delete s;
704 if(ans) return true;
705 } catch(...) {
706 delete s;
708 try {
709 zip::reader r(filename);
710 std::istream& s = r["systemid"];
711 std::string s2;
712 std::getline(s, s2);
713 delete &s;
714 istrip_CR(s2);
715 return (s2 == "lsnes-rr1");
716 } catch(...) {
717 return false;
721 class loadfile : public wxFileDropTarget
723 public:
724 loadfile(wxwin_mainwindow* win) : pwin(win) {};
725 bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
727 bool ret = false;
728 if(filenames.Count() == 2) {
729 std::string a = tostdstring(filenames[0]);
730 std::string b = tostdstring(filenames[1]);
731 bool amov = is_lsnes_movie(a);
732 bool bmov = is_lsnes_movie(b);
733 if(amov == bmov)
734 return false;
735 if(amov) std::swap(a, b);
736 runemufn_async([a, b]() {
737 lsnes_cmd.invoke("unpause-emulator");
738 romload_request req;
739 req.packfile = a;
740 load_new_rom(req);
741 lsnes_cmd.invoke("load-smart " + b);
743 ret = true;
745 if(filenames.Count() == 1) {
746 std::string a = tostdstring(filenames[0]);
747 bool amov = is_lsnes_movie(a);
748 if(amov) {
749 platform::queue("load-smart " + a);
750 pwin->recent_movies->add(a);
751 ret = true;
752 } else {
753 romload_request req;
754 req.packfile = a;
755 runemufn_async([req]() {
756 lsnes_cmd.invoke("unpause-emulator");
757 load_new_rom(req);
759 pwin->recent_roms->add(loadreq_to_multirom(req));
760 ret = true;
763 return ret;
765 wxwin_mainwindow* pwin;
769 void boot_emulator(loaded_rom& rom, moviefile& movie, bool fscreen)
771 update_preferences();
772 try {
773 struct emu_args* a = new emu_args;
774 a->rom = rom;
775 a->initial = &movie;
776 a->load_has_to_succeed = false;
777 modal_pause_holder hld;
778 emulation_thread = new thread_class(emulator_main, a);
779 main_window = new wxwin_mainwindow(fscreen);
780 main_window->Show();
781 } catch(std::bad_alloc& e) {
782 OOM_panic();
786 wxwin_mainwindow::panel::panel(wxWindow* win)
787 : wxPanel(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS)
789 this->Connect(wxEVT_PAINT, wxPaintEventHandler(panel::on_paint), NULL, this);
790 this->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(panel::on_erase), NULL, this);
791 this->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(panel::on_keyboard_down), NULL, this);
792 this->Connect(wxEVT_KEY_UP, wxKeyEventHandler(panel::on_keyboard_up), NULL, this);
793 this->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
794 this->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
795 this->Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
796 this->Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
797 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(panel::on_mouse), NULL, this);
798 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(panel::on_mouse), NULL, this);
799 this->Connect(wxEVT_MOTION, wxMouseEventHandler(panel::on_mouse), NULL, this);
800 this->Connect(wxEVT_ENTER_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
801 this->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(panel::on_mouse), NULL, this);
802 SetMinSize(wxSize(512, 448));
805 void wxwin_mainwindow::menu_start(wxString name)
807 while(!upper.empty())
808 upper.pop();
809 current_menu = new wxMenu();
810 menubar->Append(current_menu, name);
813 void wxwin_mainwindow::menu_special(wxString name, wxMenu* menu)
815 while(!upper.empty())
816 upper.pop();
817 menubar->Append(menu, name);
818 current_menu = NULL;
821 wxMenuItem* wxwin_mainwindow::menu_special_sub(wxString name, wxMenu* menu)
823 return current_menu->AppendSubMenu(menu, name);
826 void wxwin_mainwindow::menu_entry(int id, wxString name)
828 current_menu->Append(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_entry_check(int id, wxString name)
835 checkitems[id] = current_menu->AppendCheckItem(id, name);
836 Connect(id, wxEVT_COMMAND_MENU_SELECTED,
837 wxCommandEventHandler(wxwin_mainwindow::wxwin_mainwindow::handle_menu_click), NULL, this);
840 void wxwin_mainwindow::menu_start_sub(wxString name)
842 wxMenu* old = current_menu;
843 upper.push(current_menu);
844 current_menu = new wxMenu();
845 old->AppendSubMenu(current_menu, name);
848 void wxwin_mainwindow::menu_end_sub()
850 current_menu = upper.top();
851 upper.pop();
854 bool wxwin_mainwindow::menu_ischecked(int id)
856 if(checkitems.count(id))
857 return checkitems[id]->IsChecked();
858 else
859 return false;
862 void wxwin_mainwindow::menu_check(int id, bool newstate)
864 if(checkitems.count(id))
865 return checkitems[id]->Check(newstate);
866 else
867 return;
870 void wxwin_mainwindow::menu_enable(int id, bool newstate)
872 auto item = menubar->FindItem(id);
873 if(!item)
874 return;
875 item->Enable(newstate);
878 void wxwin_mainwindow::menu_separator()
880 current_menu->AppendSeparator();
883 void wxwin_mainwindow::panel::request_paint()
885 Refresh();
888 void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e)
890 if(wx_escape_count >= 3 && is_fs) {
891 //Leave fullscreen mode.
892 main_window->enter_or_leave_fullscreen(false);
894 render_framebuffer();
895 static
896 uint8_t* srcp[1];
897 int srcs[1];
898 uint8_t* dstp[1];
899 int dsts[1];
900 wxPaintDC dc(this);
901 uint32_t tw, th;
902 bool aux = hflip_enabled || vflip_enabled || rotate_enabled;
903 auto sfactors = calc_scale_factors(video_scale_factor, arcorrect_enabled, our_rom.rtype ?
904 our_rom.rtype->get_PAR() : 1.0);
905 if(rotate_enabled) {
906 tw = main_screen.get_height() * sfactors.second + 0.5;
907 th = main_screen.get_width() * sfactors.first + 0.5;
908 } else {
909 tw = main_screen.get_width() * sfactors.first + 0.5;
910 th = main_screen.get_height() * sfactors.second + 0.5;
912 if(!tw || !th) {
913 main_window_dirty = false;
914 return;
916 //Scale this to fullscreen.
917 if(is_fs) {
918 wxSize screen = main_window->GetSize();
919 double fss = min(1.0 * screen.GetWidth() / tw, 1.0 * screen.GetHeight() / th);
920 tw *= fss;
921 th *= fss;
924 if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags ||
925 hflip_enabled != old_hflip || vflip_enabled != old_vflip || rotate_enabled != old_rotate) {
926 if(screen_buffer) {
927 delete[] screen_buffer;
928 screen_buffer = NULL;
930 if(rotate_buffer) {
931 delete[] rotate_buffer;
932 rotate_buffer = NULL;
934 old_height = th;
935 old_width = tw;
936 old_flags = scaling_flags;
937 old_hflip = hflip_enabled;
938 old_vflip = vflip_enabled;
939 old_rotate = rotate_enabled;
940 uint32_t w = main_screen.get_width();
941 uint32_t h = main_screen.get_height();
942 if(w && h)
943 sws_ctx = sws_getCachedContext(sws_ctx, rotate_enabled ? h : w, rotate_enabled ? w : h,
944 PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL);
945 tw = max(tw, static_cast<uint32_t>(128));
946 th = max(th, static_cast<uint32_t>(112));
947 screen_buffer = new unsigned char[tw * th * 3];
948 if(aux)
949 rotate_buffer = new uint32_t[main_screen.get_width() * main_screen.get_height()];
950 SetMinSize(wxSize(tw, th));
951 signal_resize_needed();
953 if(aux) {
954 //Hflip, Vflip or rotate active.
955 size_t width = main_screen.get_width();
956 size_t height = main_screen.get_height();
957 size_t width1 = width - 1;
958 size_t height1 = height - 1;
959 size_t stride = main_screen.rowptr(1) - main_screen.rowptr(0);
960 uint32_t* pixels = main_screen.rowptr(0);
961 if(rotate_enabled) {
962 for(unsigned y = 0; y < height; y++) {
963 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
964 uint32_t* dpixels = rotate_buffer + (height1 - y);
965 if(hflip_enabled)
966 for(unsigned x = 0; x < width; x++)
967 dpixels[x * height] = pixels2[width1 - x];
968 else
969 for(unsigned x = 0; x < width; x++)
970 dpixels[x * height] = pixels2[x];
972 } else {
973 for(unsigned y = 0; y < height; y++) {
974 uint32_t* pixels2 = pixels + (vflip_enabled ? (height1 - y) : y) * stride;
975 uint32_t* dpixels = rotate_buffer + y * width;
976 if(hflip_enabled)
977 for(unsigned x = 0; x < width; x++)
978 dpixels[x] = pixels2[width1 - x];
979 else
980 for(unsigned x = 0; x < width; x++)
981 dpixels[x] = pixels2[x];
985 if(aux)
986 srcs[0] = 4 * (rotate_enabled ? main_screen.get_height() : main_screen.get_width());
987 else
988 srcs[0] = 4 * main_screen.get_stride();
989 dsts[0] = 3 * tw;
990 srcp[0] = reinterpret_cast<unsigned char*>(aux ? rotate_buffer : main_screen.rowptr(0));
991 dstp[0] = screen_buffer;
992 memset(screen_buffer, 0, tw * th * 3);
993 if(main_screen.get_width() && main_screen.get_height())
994 sws_scale(sws_ctx, srcp, srcs, 0, rotate_enabled ? main_screen.get_width() : main_screen.get_height(),
995 dstp, dsts);
996 wxBitmap bmp(wxImage(tw, th, screen_buffer, true));
997 dc.DrawBitmap(bmp, 0, 0, false);
998 main_window_dirty = false;
1001 void wxwin_mainwindow::panel::on_erase(wxEraseEvent& e)
1003 //Blank.
1006 void wxwin_mainwindow::panel::on_keyboard_down(wxKeyEvent& e)
1008 handle_wx_keyboard(e, true);
1011 void wxwin_mainwindow::panel::on_keyboard_up(wxKeyEvent& e)
1013 handle_wx_keyboard(e, false);
1016 void wxwin_mainwindow::panel::on_mouse(wxMouseEvent& e)
1018 handle_wx_mouse(e);
1021 wxwin_mainwindow::wxwin_mainwindow(bool fscreen)
1022 : wxFrame(NULL, wxID_ANY, getname(), wxDefaultPosition, wxSize(-1, -1),
1023 wxMINIMIZE_BOX | wxSYSTEM_MENU | wxCAPTION | wxCLIP_CHILDREN | wxCLOSE_BOX)
1025 download_in_progress = NULL;
1026 Centre();
1027 mwindow = NULL;
1028 toplevel = new wxFlexGridSizer(1, 2, 0, 0);
1029 toplevel->Add(gpanel = new panel(this), 1, wxGROW);
1030 toplevel->Add(spanel = new wxwin_status::panel(this, gpanel, 20), 1, wxGROW);
1031 spanel_shown = true;
1032 toplevel->SetSizeHints(this);
1033 SetSizer(toplevel);
1034 Fit();
1035 gpanel->SetFocus();
1036 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_mainwindow::on_close));
1037 SetMenuBar(menubar = new wxMenuBar);
1038 SetStatusBar(statusbar = new wxStatusBar(this));
1040 menu_start(wxT("File"));
1041 menu_start_sub(wxT("New"));
1042 menu_entry(wxID_NEW_MOVIE, wxT("Movie..."));
1043 menu_entry(wxID_NEW_PROJECT, wxT("Project..."));
1044 menu_end_sub();
1045 menu_start_sub(wxT("Load"));
1046 menu_entry(wxID_LOAD_STATE, wxT("State..."));
1047 menu_entry(wxID_LOAD_MOVIE, wxT("Movie..."));
1048 menu_entry(wxID_DOWNLOAD, wxT("Download movie..."));
1049 if(loadlib::library::name() != "") {
1050 menu_separator();
1051 menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + loadlib::library::name()));
1052 menu_entry(wxID_PLUGIN_MANAGER, towxstring("Plugin manager"));
1054 menu_separator();
1055 menu_entry(wxID_RELOAD_ROM_IMAGE, wxT("Reload ROM"));
1056 menu_entry(wxID_LOAD_ROM_IMAGE_FIRST, wxT("ROM..."));
1057 menu_special_sub(wxT("Multifile ROM"), loadroms = new loadrom_menu(this, wxID_LOAD_ROM_IMAGE_FIRST + 1,
1058 wxID_LOAD_ROM_IMAGE_LAST, [this](core_type* t) { this->do_load_rom_image(t); }));
1059 menu_special_sub(wxT("Project"), projects = new projects_menu(this, wxID_PROJECT_FIRST, wxID_PROJECT_LAST,
1060 get_config_path() + "/recent-projects.txt", [this](const std::string& id) {
1061 this->project_selected(id); }));
1062 menu_separator();
1063 menu_special_sub(wxT("Recent ROMs"), recent_roms = new recent_menu<recentfiles::multirom>(this,
1064 wxID_RROM_FIRST, wxID_RROM_LAST, get_config_path() + "/recent-roms.txt", recent_rom_selected));
1065 menu_special_sub(wxT("Recent Movies"), recent_movies = new recent_menu<recentfiles::path>(this,
1066 wxID_RMOVIE_FIRST, wxID_RMOVIE_LAST, get_config_path() + "/recent-movies.txt",
1067 recent_movie_selected));
1068 menu_special_sub(wxT("Recent Lua scripts"), recent_scripts = new recent_menu<recentfiles::path>(this,
1069 wxID_RLUA_FIRST, wxID_RLUA_LAST, get_config_path() + "/recent-scripts.txt",
1070 recent_script_selected));
1071 menu_separator();
1072 menu_entry(wxID_CONFLICTRESOLUTION, wxT("Conflict resolution"));
1073 menu_separator();
1074 branches_menu* brlist;
1075 auto brlist_item = menu_special_sub(wxT("Branches"), brlist = new branches_menu(this, wxID_BRANCH_FIRST,
1076 wxID_BRANCH_LAST));
1077 brlist->set_disabler([brlist_item](bool enabled) { brlist_item->Enable(enabled); });
1078 brlist->update();
1079 menu_end_sub();
1080 menu_start_sub(wxT("Save"));
1081 menu_entry(wxID_SAVE_STATE, wxT("State..."));
1082 menu_entry(wxID_SAVE_MOVIE, wxT("Movie..."));
1083 menu_entry(wxID_SAVE_SCREENSHOT, wxT("Screenshot..."));
1084 menu_entry(wxID_SAVE_SUBTITLES, wxT("Subtitles..."));
1085 menu_entry(wxID_CANCEL_SAVES, wxT("Cancel pending saves"));
1086 menu_separator();
1087 menu_entry(wxID_CHDIR, wxT("Change working directory..."));
1088 menu_separator();
1089 menu_special_sub(wxT("Upload"), new upload_menu(this, wxID_UPLOAD_FIRST, wxID_UPLOAD_LAST));
1090 menu_end_sub();
1091 menu_start_sub(wxT("Close"));
1092 menu_entry(wxID_CLOSE_PROJECT, wxT("Project"));
1093 menu_entry(wxID_CLOSE_ROM, wxT("ROM"));
1094 menu_enable(wxID_CLOSE_PROJECT, project_get() != NULL);
1095 menu_enable(wxID_CLOSE_ROM, project_get() == NULL);
1096 menu_end_sub();
1097 menu_separator();
1098 menu_entry(wxID_EXIT, wxT("Quit"));
1100 menu_special(wxT("System"), reinterpret_cast<wxMenu*>(sysmenu = new system_menu(this)));
1102 menu_start(wxT("Movie"));
1103 menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode"));
1104 menu_check(wxID_READONLY_MODE, is_readonly_mode());
1105 menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors..."));
1106 menu_entry(wxID_EDIT_SUBTITLES, wxT("Edit subtitles..."));
1107 menu_entry(wxID_EDIT_VSUBTITLES, wxT("Edit commantary track..."));
1108 menu_separator();
1109 menu_entry(wxID_REWIND_MOVIE, wxT("Rewind to start"));
1111 menu_start(wxT("Speed"));
1112 menu_entry(wxID_SPEED_5, wxT("1/20x"));
1113 menu_entry(wxID_SPEED_10, wxT("1/10x"));
1114 menu_entry(wxID_SPEED_17, wxT("1/6x"));
1115 menu_entry(wxID_SPEED_20, wxT("1/5x"));
1116 menu_entry(wxID_SPEED_25, wxT("1/4x"));
1117 menu_entry(wxID_SPEED_33, wxT("1/3x"));
1118 menu_entry(wxID_SPEED_50, wxT("1/2x"));
1119 menu_entry(wxID_SPEED_100, wxT("1x"));
1120 menu_entry(wxID_SPEED_150, wxT("1.5x"));
1121 menu_entry(wxID_SPEED_200, wxT("2x"));
1122 menu_entry(wxID_SPEED_300, wxT("3x"));
1123 menu_entry(wxID_SPEED_500, wxT("5x"));
1124 menu_entry(wxID_SPEED_1000, wxT("10x"));
1125 menu_entry(wxID_SPEED_TURBO, wxT("Turbo"));
1126 menu_entry(wxID_SET_SPEED, wxT("Set..."));
1128 menu_start(wxT("Tools"));
1129 menu_entry(wxID_RUN_SCRIPT, wxT("Run batch file..."));
1130 menu_separator();
1131 menu_entry(wxID_EVAL_LUA, wxT("Evaluate Lua statement..."));
1132 menu_entry(wxID_RUN_LUA, wxT("Run Lua script..."));
1133 menu_separator();
1134 menu_entry(wxID_RESET_LUA, wxT("Reset Lua VM"));
1135 menu_separator();
1136 menu_entry(wxID_AUTOHOLD, wxT("Autohold/Autofire..."));
1137 menu_entry(wxID_TASINPUT, wxT("TAS input plugin..."));
1138 menu_entry(wxID_MULTITRACK, wxT("Multitrack..."));
1139 menu_entry(wxID_EDIT_MACROS, wxT("Edit macros..."));
1140 menu_separator();
1141 menu_entry(wxID_EDIT_MEMORYWATCH, wxT("Edit memory watch..."));
1142 menu_separator();
1143 menu_entry(wxID_LOAD_MEMORYWATCH, wxT("Load memory watch..."));
1144 menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch..."));
1145 menu_separator();
1146 menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search..."));
1147 menu_entry(wxID_HEXEDITOR, wxT("Memory editor..."));
1148 tracelog_menu* trlog;
1149 auto trlog_item = menu_special_sub(wxT("Trace log"), trlog = new tracelog_menu(this, wxID_TRACELOG_FIRST,
1150 wxID_TRACELOG_LAST));
1151 trlog->set_disabler([trlog_item](bool enabled) { trlog_item->Enable(enabled); });
1152 trlog->update();
1153 menu_entry(wxID_DISASSEMBLER, wxT("Disassembler..."));
1154 menu_separator();
1155 menu_entry(wxID_MOVIE_EDIT, wxT("Edit movie..."));
1156 menu_separator();
1157 menu_special_sub(wxT("Video Capture"), reinterpret_cast<dumper_menu*>(dmenu = new dumper_menu(this,
1158 wxID_DUMP_FIRST, wxID_DUMP_LAST)));
1160 menu_start(wxT("Configure"));
1161 menu_entry_check(wxID_SHOW_STATUS, wxT("Show status panel"));
1162 menu_check(wxID_SHOW_STATUS, true);
1163 menu_entry_check(wxID_DEDICATED_MEMORY_WATCH, wxT("Dedicated memory watch"));
1164 menu_entry(wxID_SHOW_MESSAGES, wxT("Show messages"));
1165 menu_special_sub(wxT("Settings"), new settings_menu(this, wxID_SETTINGS_FIRST));
1166 if(audioapi_driver_initialized()) {
1167 menu_separator();
1168 menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled"));
1169 menu_entry(wxID_VUDISPLAY, wxT("VU display / sound controls"));
1170 menu_check(wxID_AUDIO_ENABLED, platform::is_sound_enabled());
1172 menu_separator();
1173 menu_entry(wxID_ENTER_FULLSCREEN, wxT("Enter fullscreen mode"));
1175 menu_start(wxT("Help"));
1176 menu_entry(wxID_ABOUT, wxT("About..."));
1178 corechange.set(notify_core_change, []() { signal_core_change(); });
1179 titlechange.set(notify_title_change, []() { signal_core_change(); });
1180 newcore.set(notify_new_core, []() { update_preferences(); });
1181 unmuted.set(notify_sound_unmute, [this](bool unmute) {
1182 runuifun([this, unmute]() { this->menu_check(wxID_AUDIO_ENABLED, unmute); });
1184 modechange.set(notify_mode_change, [this](bool readonly) {
1185 runuifun([this, readonly]() { this->menu_check(wxID_READONLY_MODE, readonly); });
1187 gpanel->SetDropTarget(new loadfile(this));
1188 spanel->SetDropTarget(new loadfile(this));
1189 set_hasher_callback(hash_callback);
1190 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1191 menubar->SetMenuLabel(1, towxstring(our_rom.rtype->get_systemmenu_name()));
1192 focus_timer = new _focus_timer;
1193 if(fscreen) {
1194 wx_escape_count = 0;
1195 enter_or_leave_fullscreen(true);
1199 wxwin_mainwindow::~wxwin_mainwindow()
1201 if(sws_ctx) sws_freeContext(sws_ctx);
1202 if(screen_buffer) delete[] screen_buffer;
1203 if(rotate_buffer) delete[] rotate_buffer;
1206 void wxwin_mainwindow::request_paint()
1208 gpanel->Refresh();
1211 void wxwin_mainwindow::on_close(wxCloseEvent& e)
1213 //Veto it for now, latter things will delete it.
1214 e.Veto();
1215 platform::queue("quit-emulator");
1218 void wxwin_mainwindow::notify_update() throw()
1220 if(!main_window_dirty) {
1221 main_window_dirty = true;
1222 gpanel->Refresh();
1226 void wxwin_mainwindow::notify_resized() throw()
1228 toplevel->Layout();
1229 toplevel->SetSizeHints(this);
1230 Fit();
1233 void wxwin_mainwindow::notify_update_status() throw()
1235 spanel->request_paint();
1236 if(mwindow)
1237 mwindow->notify_update();
1240 void wxwin_mainwindow::notify_exit() throw()
1242 wxwidgets_exiting = true;
1243 join_emulator_thread();
1244 Destroy();
1247 std::u32string read_variable_map(const std::map<std::string, std::u32string>& vars, const std::string& key)
1249 if(!vars.count(key))
1250 return U"";
1251 return vars.find(key)->second;
1254 void wxwin_mainwindow::update_statusbar(const std::map<std::string, std::u32string>& vars)
1256 if(download_in_progress) {
1257 statusbar->SetStatusText(towxstring(download_in_progress->statusmsg()));
1258 return;
1260 if(hashing_in_progress) {
1261 //TODO: Display this as a dialog.
1262 std::ostringstream s;
1263 s << "Hashing ROMs, approximately " << ((hashing_left + 524288) >> 20) << " of "
1264 << ((hashing_total + 524288) >> 20) << "MB left...";
1265 statusbar->SetStatusText(towxstring(s.str()));
1266 return;
1268 if(vars.empty())
1269 return;
1270 try {
1271 std::basic_ostringstream<char32_t> s;
1272 bool recording = (read_variable_map(vars, "!mode") == U"R");
1273 if(recording)
1274 s << U"Frame: " << read_variable_map(vars, "!frame");
1275 else
1276 s << U"Frame: " << read_variable_map(vars, "!frame") << U"/" <<
1277 read_variable_map(vars, "!length");
1278 s << U" Lag: " << read_variable_map(vars, "!lag");
1279 s << U" Subframe: " << read_variable_map(vars, "!subframe");
1280 if(vars.count("!saveslot")) {
1281 s << U" Slot: ";
1282 if(vars.count("!branch")) s << read_variable_map(vars, "!branch") << U"ā†’";
1283 s << read_variable_map(vars, "!saveslot");
1285 if(vars.count("!saveslotinfo"))
1286 s << U" [" << read_variable_map(vars, "!saveslotinfo") << U"]";
1287 s << U" Speed: " << read_variable_map(vars, "!speed") << U"%";
1288 s << U" ";
1289 if(read_variable_map(vars, "!pause") == U"B")
1290 s << U" Breakpoint";
1291 else if(read_variable_map(vars, "!pause") == U"P")
1292 s << U" Paused";
1293 if(read_variable_map(vars, "!dumping") != U"")
1294 s << U" Dumping";
1295 if(read_variable_map(vars, "!mode") == U"C")
1296 s << U" Corrupt";
1297 else if(read_variable_map(vars, "!mode") == U"R")
1298 s << U" Recording";
1299 else if(read_variable_map(vars, "!mode") == U"P")
1300 s << U" Playback";
1301 else if(read_variable_map(vars, "!mode") == U"F")
1302 s << U" Finished";
1303 else
1304 s << U" Unknown";
1305 if(vars.count("!mbranch"))
1306 s << U" Branch: " << read_variable_map(vars, "!mbranch");
1307 std::u32string macros = read_variable_map(vars, "!macros");
1308 if(macros.length())
1309 s << U" Macros: " << macros;
1311 statusbar->SetStatusText(towxstring(s.str()));
1312 } catch(std::exception& e) {
1316 #define NEW_KEYBINDING "A new binding..."
1317 #define NEW_ALIAS "A new alias..."
1318 #define NEW_WATCH "A new watch..."
1320 void wxwin_mainwindow::handle_menu_click(wxCommandEvent& e)
1322 try {
1323 handle_menu_click_cancelable(e);
1324 } catch(canceled_exception& e) {
1325 //Ignore.
1326 } catch(std::bad_alloc& e) {
1327 OOM_panic();
1328 } catch(std::exception& e) {
1329 show_message_ok(this, "Error in menu handler", e.what(), wxICON_EXCLAMATION);
1333 void wxwin_mainwindow::refresh_title() throw()
1335 SetTitle(getname());
1336 auto p = project_get();
1337 menu_enable(wxID_RELOAD_ROM_IMAGE, !p);
1338 for(int i = wxID_LOAD_ROM_IMAGE_FIRST; i <= wxID_LOAD_ROM_IMAGE_LAST; i++)
1339 menu_enable(i, !p);
1340 menu_enable(wxID_CLOSE_PROJECT, p != NULL);
1341 menu_enable(wxID_CLOSE_ROM, p == NULL);
1342 reinterpret_cast<system_menu*>(sysmenu)->update(false);
1343 menubar->SetMenuLabel(1, towxstring(our_rom.rtype->get_systemmenu_name()));
1346 namespace
1348 struct movie_or_savestate
1350 public:
1351 typedef std::pair<std::string,std::string> returntype;
1352 movie_or_savestate(bool is_state)
1354 state = is_state;
1356 filedialog_input_params input(bool save) const
1358 filedialog_input_params p;
1359 std::string ext = state ? project_savestate_ext() : "lsmv";
1360 std::string name = state ? "Savestates" : "Movies";
1361 if(save) {
1362 p.types.push_back(filedialog_type_entry(name, "*." + ext, ext));
1363 p.types.push_back(filedialog_type_entry(name + " (binary)", "*." + ext, ext));
1364 } else
1365 p.types.push_back(filedialog_type_entry(name, "*." + ext + ";*." + ext + ".backup",
1366 ext));
1367 if(!save && state) {
1368 p.types.push_back(filedialog_type_entry("Savestates [read only]", "*." + ext +
1369 ";*." + ext + ".backup", ext));
1370 p.types.push_back(filedialog_type_entry("Savestates [read-write]", "*." + ext +
1371 ";*." + ext + ".backup", ext));
1372 p.types.push_back(filedialog_type_entry("Savestates [preserve]", "*." + ext +
1373 ";*." + ext + ".backup", ext));
1374 p.types.push_back(filedialog_type_entry("Savestates [all branches]", "*." + ext +
1375 ";*." + ext + ".backup", ext));
1377 p.default_type = save ? (state ? save_dflt_binary : movie_dflt_binary) : 0;
1378 return p;
1380 std::pair<std::string, std::string> output(const filedialog_output_params& p, bool save) const
1382 std::string cmdmod;
1383 if(save)
1384 cmdmod = p.typechoice ? "-binary" : "-zip";
1385 else if(state)
1386 switch(p.typechoice) {
1387 case 0: cmdmod = ""; break;
1388 case 1: cmdmod = "-readonly"; break;
1389 case 2: cmdmod = "-state"; break;
1390 case 3: cmdmod = "-preserve"; break;
1391 case 4: cmdmod = "-allbranches"; break;
1393 return std::make_pair(cmdmod, p.path);
1395 private:
1396 bool state;
1398 struct movie_or_savestate filetype_movie(false);
1399 struct movie_or_savestate filetype_savestate(true);
1402 void wxwin_mainwindow::project_selected(const std::string& id)
1404 std::string filename, displayname;
1405 bool load_ok = false;
1406 runemufn([id, &filename, &displayname, &load_ok]() -> void {
1407 try {
1408 auto& p = project_load(id); //Check.
1409 filename = p.filename;
1410 displayname = p.name;
1411 load_ok = true;
1412 delete &p;
1413 switch_projects(id);
1414 } catch(std::exception& e) {
1415 messages << "Failed to change project: " << e.what() << std::endl;
1418 if(load_ok) {
1419 recentfiles::namedobj obj;
1420 obj._id = id;
1421 obj._filename = filename;
1422 obj._display = displayname;
1423 projects->add(obj);
1427 void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e)
1429 std::string filename;
1430 std::pair<std::string, std::string> filename2;
1431 bool s;
1432 switch(e.GetId()) {
1433 case wxID_FRAMEADVANCE:
1434 platform::queue("+advance-frame");
1435 platform::queue("-advance-frame");
1436 return;
1437 case wxID_SUBFRAMEADVANCE:
1438 platform::queue("+advance-poll");
1439 platform::queue("-advance-poll");
1440 return;
1441 case wxID_NEXTPOLL:
1442 platform::queue("advance-skiplag");
1443 return;
1444 case wxID_PAUSE:
1445 platform::queue("pause-emulator");
1446 return;
1447 case wxID_EXIT:
1448 platform::queue("quit-emulator");
1449 return;
1450 case wxID_AUDIO_ENABLED:
1451 platform::sound_enable(menu_ischecked(wxID_AUDIO_ENABLED));
1452 return;
1453 case wxID_CANCEL_SAVES:
1454 platform::queue("cancel-saves");
1455 return;
1456 case wxID_LOAD_MOVIE:
1457 filename = choose_file_load(this, "Load Movie", project_moviepath(), filetype_movie).second;
1458 recent_movies->add(filename);
1459 platform::queue("load-movie " + filename);
1460 return;
1461 case wxID_LOAD_STATE:
1462 filename2 = choose_file_load(this, "Load State", project_moviepath(), filetype_savestate);
1463 recent_movies->add(filename2.second);
1464 platform::queue("load" + filename2.first + " " + filename2.second);
1465 return;
1466 case wxID_REWIND_MOVIE:
1467 platform::queue("rewind-movie");
1468 return;
1469 case wxID_SAVE_MOVIE:
1470 filename2 = choose_file_save(this, "Save Movie", project_moviepath(), filetype_movie,
1471 project_prefixname("lsmv"));
1472 recent_movies->add(filename2.second);
1473 platform::queue("save-movie" + filename2.first + " " + filename2.second);
1474 return;
1475 case wxID_SAVE_SUBTITLES:
1476 platform::queue("save-subtitle " + choose_file_save(this, "Save subtitles", project_moviepath(),
1477 filetype_sub, project_prefixname("sub")));
1478 return;
1479 case wxID_SAVE_STATE:
1480 filename2 = choose_file_save(this, "Save State", project_moviepath(), filetype_savestate);
1481 recent_movies->add(filename2.second);
1482 platform::queue("save-state" + filename2.first + " " + filename2.second);
1483 return;
1484 case wxID_SAVE_SCREENSHOT:
1485 platform::queue("take-screenshot " + choose_file_save(this, "Save Screenshot", project_moviepath(),
1486 filetype_png, get_default_screenshot_name()));
1487 return;
1488 case wxID_RUN_SCRIPT:
1489 platform::queue("run-script " + pick_file_member(this, "Select Script", project_otherpath()));
1490 return;
1491 case wxID_RUN_LUA: {
1492 std::string f = choose_file_load(this, "Select Lua Script", project_otherpath(),
1493 filetype_lua_script);
1494 platform::queue("run-lua " + f);
1495 recent_scripts->add(f);
1496 return;
1498 case wxID_RESET_LUA:
1499 platform::queue("reset-lua");
1500 return;
1501 case wxID_EVAL_LUA:
1502 platform::queue("evaluate-lua " + pick_text(this, "Evaluate Lua", "Enter Lua Statement:"));
1503 return;
1504 case wxID_READONLY_MODE:
1505 s = menu_ischecked(wxID_READONLY_MODE);
1506 runemufn([s]() {
1507 if(!s)
1508 lua_callback_movie_lost("readwrite");
1509 if(movb) movb.get_movie().readonly_mode(s);
1510 notify_mode_change(s);
1511 if(!s)
1512 lua_callback_do_readwrite();
1513 update_movie_state();
1514 graphics_driver_notify_status();
1516 return;
1517 case wxID_AUTOHOLD:
1518 wxeditor_autohold_display(this);
1519 return;
1520 case wxID_EDIT_AUTHORS:
1521 wxeditor_authors_display(this);
1522 return;
1523 case wxID_EDIT_MACROS:
1524 wxeditor_macro_display(this);
1525 return;
1526 case wxID_EDIT_SUBTITLES:
1527 wxeditor_subtitles_display(this);
1528 return;
1529 case wxID_EDIT_VSUBTITLES:
1530 show_wxeditor_voicesub(this);
1531 return;
1532 case wxID_EDIT_MEMORYWATCH:
1533 wxeditor_memorywatches_display(this);
1534 return;
1535 case wxID_SAVE_MEMORYWATCH: {
1536 modal_pause_holder hld;
1537 std::set<std::string> old_watches;
1538 runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); });
1539 std::string filename = choose_file_save(this, "Save watches to file", project_otherpath(),
1540 filetype_watch);
1541 std::ofstream out(filename.c_str());
1542 for(auto i : old_watches) {
1543 std::string val;
1544 runemufn([i, &val]() {
1545 try {
1546 val = lsnes_memorywatch.get_string(i);
1547 } catch(std::exception& e) {
1548 messages << "Can't get value of watch '" << i << "': " << e.what()
1549 << std::endl;
1552 out << i << std::endl << val << std::endl;
1554 out.close();
1555 return;
1557 case wxID_LOAD_MEMORYWATCH: {
1558 modal_pause_holder hld;
1559 std::set<std::string> old_watches;
1560 runemufn([&old_watches]() { old_watches = lsnes_memorywatch.enumerate(); });
1561 std::map<std::string, std::string> new_watches;
1562 std::string filename = choose_file_load(this, "Choose memory watch file", project_otherpath(),
1563 filetype_watch);
1564 try {
1565 std::istream& in = zip::openrel(filename, "");
1566 while(in) {
1567 std::string wname;
1568 std::string wexpr;
1569 std::getline(in, wname);
1570 std::getline(in, wexpr);
1571 new_watches[strip_CR(wname)] = strip_CR(wexpr);
1573 delete &in;
1574 } catch(std::exception& e) {
1575 show_message_ok(this, "Error", std::string("Can't load memory watch: ") + e.what(),
1576 wxICON_EXCLAMATION);
1577 return;
1580 runemufn([&new_watches, &old_watches]() {
1581 handle_watch_load(new_watches, old_watches);
1583 return;
1585 case wxID_MEMORY_SEARCH:
1586 wxwindow_memorysearch_display();
1587 return;
1588 case wxID_TASINPUT:
1589 wxeditor_tasinput_display(this);
1590 return;
1591 case wxID_ABOUT: {
1592 std::ostringstream str;
1593 str << "Version: lsnes rr" << lsnes_version << std::endl;
1594 str << "Revision: " << lsnes_git_revision << std::endl;
1595 for(auto i : core_core::all_cores())
1596 if(!i->is_hidden())
1597 str << "Core: " << i->get_core_identifier() << std::endl;
1598 wxMessageBox(towxstring(str.str()), _T("About"), wxICON_INFORMATION | wxOK, this);
1599 return;
1601 case wxID_SHOW_STATUS: {
1602 bool newstate = menu_ischecked(wxID_SHOW_STATUS);
1603 if(newstate)
1604 spanel->Show();
1605 if(newstate && !spanel_shown)
1606 toplevel->Add(spanel, 1, wxGROW);
1607 else if(!newstate && spanel_shown)
1608 toplevel->Detach(spanel);
1609 if(!newstate)
1610 spanel->Hide();
1611 spanel_shown = newstate;
1612 toplevel->Layout();
1613 toplevel->SetSizeHints(this);
1614 Fit();
1615 return;
1617 case wxID_DEDICATED_MEMORY_WATCH: {
1618 bool newstate = menu_ischecked(wxID_DEDICATED_MEMORY_WATCH);
1619 if(newstate && !mwindow) {
1620 mwindow = new wxwin_status(-1, "Memory Watch");
1621 spanel->set_watch_flag(1);
1622 mwindow->Show();
1623 } else if(!newstate && mwindow) {
1624 mwindow->Destroy();
1625 mwindow = NULL;
1626 spanel->set_watch_flag(0);
1628 return;
1630 case wxID_SET_SPEED: {
1631 std::string value = "infinite";
1632 double val = get_speed_multiplier();
1633 if(!(val == std::numeric_limits<double>::infinity()))
1634 value = (stringfmt() << (100 * val)).str();
1635 value = pick_text(this, "Set speed", "Enter percentage speed (or \"infinite\"):", value);
1636 try {
1637 if(value == "infinite")
1638 set_speed_multiplier(std::numeric_limits<double>::infinity());
1639 else {
1640 double v = parse_value<double>(value) / 100;
1641 if(v <= 0.0001)
1642 throw 42;
1643 set_speed_multiplier(v);
1645 } catch(...) {
1646 wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this);
1648 return;
1650 case wxID_SPEED_5:
1651 set_speed(5);
1652 break;
1653 case wxID_SPEED_10:
1654 set_speed(10);
1655 break;
1656 case wxID_SPEED_17:
1657 set_speed(16.66666666666);
1658 break;
1659 case wxID_SPEED_20:
1660 set_speed(20);
1661 break;
1662 case wxID_SPEED_25:
1663 set_speed(25);
1664 break;
1665 case wxID_SPEED_33:
1666 set_speed(33.3333333333333);
1667 break;
1668 case wxID_SPEED_50:
1669 set_speed(50);
1670 break;
1671 case wxID_SPEED_100:
1672 set_speed(100);
1673 break;
1674 case wxID_SPEED_150:
1675 set_speed(150);
1676 break;
1677 case wxID_SPEED_200:
1678 set_speed(200);
1679 break;
1680 case wxID_SPEED_300:
1681 set_speed(300);
1682 break;
1683 case wxID_SPEED_500:
1684 set_speed(500);
1685 break;
1686 case wxID_SPEED_1000:
1687 set_speed(1000);
1688 break;
1689 case wxID_SPEED_TURBO:
1690 set_speed(-1);
1691 break;
1692 case wxID_LOAD_LIBRARY: {
1693 std::string name = std::string("load ") + loadlib::library::name();
1694 with_loaded_library(*new loadlib::module(loadlib::library(choose_file_load(this, name,
1695 project_otherpath(), single_type(loadlib::library::extension(),
1696 loadlib::library::name())))));
1697 handle_post_loadlibrary();
1698 break;
1700 case wxID_PLUGIN_MANAGER:
1701 wxeditor_plugin_manager_display(this);
1702 return;
1703 case wxID_RELOAD_ROM_IMAGE:
1704 runemufn([]() {
1705 lsnes_cmd.invoke("unpause-emulator");
1706 reload_current_rom();
1708 return;
1709 case wxID_NEW_MOVIE:
1710 show_projectwindow(this);
1711 return;
1712 case wxID_SHOW_MESSAGES:
1713 msg_window->reshow();
1714 return;
1715 case wxID_CONFLICTRESOLUTION:
1716 show_conflictwindow(this);
1717 return;
1718 case wxID_VUDISPLAY:
1719 open_vumeter_window(this);
1720 return;
1721 case wxID_DISASSEMBLER:
1722 wxeditor_disassembler_display(this);
1723 return;
1724 case wxID_MOVIE_EDIT:
1725 wxeditor_movie_display(this);
1726 return;
1727 case wxID_NEW_PROJECT:
1728 open_new_project_window(this);
1729 return;
1730 case wxID_CLOSE_PROJECT:
1731 runemufn([]() -> void { project_set(NULL); });
1732 return;
1733 case wxID_CLOSE_ROM:
1734 runemufn([]() -> void { close_rom(); });
1735 return;
1736 case wxID_ENTER_FULLSCREEN:
1737 wx_escape_count = 0;
1738 enter_or_leave_fullscreen(true);
1739 return;
1740 case wxID_LOAD_ROM_IMAGE_FIRST:
1741 do_load_rom_image(NULL);
1742 return;
1743 case wxID_HEXEDITOR:
1744 wxeditor_hexedit_display(this);
1745 return;
1746 case wxID_MULTITRACK:
1747 wxeditor_multitrack_display(this);
1748 return;
1749 case wxID_CHDIR: {
1750 wxDirDialog* d = new wxDirDialog(this, wxT("Change working directory"), wxT("."), wxDD_DIR_MUST_EXIST);
1751 if(d->ShowModal() == wxID_CANCEL) {
1752 d->Destroy();
1753 return;
1755 std::string path = tostdstring(d->GetPath());
1756 d->Destroy();
1757 chdir(path.c_str());
1758 messages << "Changed working directory to '" << path << "'" << std::endl;
1759 return;
1761 case wxID_DOWNLOAD: {
1762 if(download_in_progress) return;
1763 filename = pick_text(this, "Download movie", "Enter URL to download");
1764 download_in_progress = new file_download();
1765 download_in_progress->url = filename;
1766 download_in_progress->target_slot = "wxwidgets_download_tmp";
1767 download_in_progress->do_async();
1768 new download_timer(this);
1769 return;
1774 void wxwin_mainwindow::action_updated()
1776 reinterpret_cast<system_menu*>(sysmenu)->update(true);
1779 void wxwin_mainwindow::enter_or_leave_fullscreen(bool fs)
1781 if(fs && !is_fs) {
1782 if(spanel_shown)
1783 toplevel->Detach(spanel);
1784 spanel->Hide();
1785 is_fs = fs;
1786 ShowFullScreen(true);
1787 Fit();
1788 } else if(!fs && is_fs) {
1789 ShowFullScreen(false);
1790 if(spanel_shown) {
1791 spanel->Show();
1792 toplevel->Add(spanel, 1, wxGROW);
1794 Fit();
1795 is_fs = fs;