5 #include "core/command.hpp"
6 #include "core/controller.hpp"
7 #include "core/dispatch.hpp"
8 #include "core/framerate.hpp"
9 #include "core/joystickapi.hpp"
10 #include "core/keymapper.hpp"
11 #include "core/loadlib.hpp"
12 #include "lua/lua.hpp"
13 #include "core/mainloop.hpp"
14 #include "core/misc.hpp"
15 #include "core/moviedata.hpp"
16 #include "core/rom.hpp"
17 #include "core/rrdata.hpp"
18 #include "core/settings.hpp"
19 #include "core/window.hpp"
20 #include "interface/romtype.hpp"
21 #include "library/string.hpp"
22 #include "library/threadtypes.hpp"
23 #include "library/utf8.hpp"
24 #include "library/zip.hpp"
26 #include "platform/wxwidgets/settings-common.hpp"
27 #include "platform/wxwidgets/platform.hpp"
28 #include "platform/wxwidgets/window_messages.hpp"
29 #include "platform/wxwidgets/window_status.hpp"
30 #include "platform/wxwidgets/window_mainwindow.hpp"
33 #include <boost/lexical_cast.hpp>
37 #include <wx/control.h>
38 #include <wx/combobox.h>
39 #include <wx/cmdline.h>
42 #define UISERV_REFRESH_TITLE 9990
43 #define UISERV_RESIZED 9991
44 #define UISERV_UIFUN 9992
45 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
46 #define UISERV_EXIT 9994
47 #define UISERV_UPDATE_STATUS 9995
48 #define UISERV_UPDATE_MESSAGES 9996
49 #define UISERV_UPDATE_SCREEN 9997
50 #define UISERV_PANIC 9998
51 #define UISERV_ERROR 9999
53 wxwin_messages
* msg_window
;
54 wxwin_mainwindow
* main_window
;
55 std::string our_rom_name
;
57 bool wxwidgets_exiting
= false;
61 threadid_class ui_thread
;
62 volatile bool panic_ack
= false;
63 std::string error_message_text
;
64 volatile bool modal_dialog_confirm
;
65 volatile bool modal_dialog_active
;
67 cv_class ui_condition
;
68 thread_class
* joystick_thread_handle
;
70 void* joystick_thread(int _args
)
72 joystick_driver_thread_fn();
76 struct uiserv_event
: public wxEvent
78 uiserv_event(int code
)
83 wxEvent
* Clone() const
85 return new uiserv_event(*this);
89 class ui_services_type
: public wxEvtHandler
91 bool ProcessEvent(wxEvent
& event
);
100 std::list
<ui_queue_entry
> ui_queue
;
102 bool ui_services_type::ProcessEvent(wxEvent
& event
)
104 int c
= event
.GetId();
105 if(c
== UISERV_PANIC
) {
107 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR
|
110 } else if(c
== UISERV_REFRESH_TITLE
) {
112 main_window
->refresh_title();
113 } else if(c
== UISERV_RESIZED
) {
115 main_window
->notify_resized();
116 } else if(c
== UISERV_ERROR
) {
117 std::string text
= error_message_text
;
118 wxMessageBox(towxstring(text
), _T("lsnes: Error"), wxICON_EXCLAMATION
| wxOK
, main_window
);
119 } else if(c
== UISERV_UPDATE_MESSAGES
) {
121 msg_window
->notify_update();
122 } else if(c
== UISERV_UPDATE_STATUS
) {
124 main_window
->notify_update_status();
125 wxeditor_movie_update();
126 wxeditor_hexeditor_update();
127 } else if(c
== UISERV_UPDATE_SCREEN
) {
129 main_window
->notify_update();
130 wxwindow_memorysearch_update();
131 wxwindow_tasinput_update();
132 } else if(c
== UISERV_EXIT
) {
134 main_window
->notify_exit();
135 } else if(c
== UISERV_UIFUN
) {
136 std::list
<ui_queue_entry
>::iterator i
;
138 queue_synchronous_fn_warning
= true;
141 umutex_class
h(ui_mutex
);
144 i
= ui_queue
.begin();
151 queue_synchronous_fn_warning
= false;
156 ui_services_type
* ui_services
;
158 void post_ui_event(int code
)
160 uiserv_event
uic(code
);
161 wxPostEvent(ui_services
, uic
);
164 void handle_config_line(std::string line
)
167 if(r
= regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
168 lsnes_vsetc
.set(r
[1], r
[2], true);
169 messages
<< "Setting " << r
[1] << " set to " << r
[2] << std::endl
;
170 } else if(r
= regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
171 if(!lsnes_cmd
.valid_alias_name(r
[1])) {
172 messages
<< "Illegal alias name " << r
[1] << std::endl
;
175 std::string tmp
= lsnes_cmd
.get_alias_for(r
[1]);
176 tmp
= tmp
+ r
[2] + "\n";
177 lsnes_cmd
.set_alias_for(r
[1], tmp
);
178 messages
<< r
[1] << " aliased to " << r
[2] << std::endl
;
179 } else if(r
= regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line
)) {
180 lsnes_mapper
.bind(r
[1], r
[2], r
[3], r
[4]);
181 if(r
[1] != "" || r
[2] != "")
182 messages
<< r
[1] << "/" << r
[2] << " ";
183 messages
<< r
[3] << " bound to '" << r
[4] << "'" << std::endl
;
184 } else if(r
= regex("BUTTON[ \t]+([^ \t]+)[ \t](.*)", line
)) {
185 controller_key
* ckey
= lsnes_mapper
.get_controllerkey(r
[2]);
188 messages
<< r
[1] << " bound (button) to " << r
[2] << std::endl
;
190 button_keys
[r
[2]] = r
[1];
191 } else if(r
= regex("PREFER[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
193 core_selections
[r
[1]] = r
[2];
194 messages
<< "Prefer " << r
[2] << " for " << r
[1] << std::endl
;
197 (stringfmt() << "Unrecognized directive: " << line
).throwex();
200 void load_configuration()
202 std::string cfg
= get_config_path() + "/lsneswxw.cfg";
203 std::ifstream
cfgfile(cfg
.c_str());
206 while(std::getline(cfgfile
, line
)) {
208 handle_config_line(line
);
209 } catch(std::exception
& e
) {
210 messages
<< "Error processing line " << lineno
<< ": " << e
.what() << std::endl
;
214 refresh_alias_binds();
217 void save_configuration()
219 std::string cfg
= get_config_path() + "/lsneswxw.cfg";
220 std::ofstream
cfgfile(cfg
.c_str());
222 for(auto i
: lsnes_vsetc
.get_all())
223 cfgfile
<< "SET " << i
.first
<< " " << i
.second
<< std::endl
;
225 for(auto i
: lsnes_cmd
.get_aliases()) {
226 std::string old_alias_value
= lsnes_cmd
.get_alias_for(i
);
227 while(old_alias_value
!= "") {
228 std::string aliasline
;
229 size_t s
= old_alias_value
.find_first_of("\n");
230 if(s
< old_alias_value
.length()) {
231 aliasline
= old_alias_value
.substr(0, s
);
232 old_alias_value
= old_alias_value
.substr(s
+ 1);
234 aliasline
= old_alias_value
;
235 old_alias_value
= "";
237 cfgfile
<< "ALIAS " << i
<< " " << aliasline
<< std::endl
;
241 for(auto i
: lsnes_mapper
.get_bindings())
242 cfgfile
<< "BIND " << std::string(i
) << " " << lsnes_mapper
.get(i
) << std::endl
;
244 for(auto i
: lsnes_mapper
.get_controller_keys()) {
247 while((b
= i
->get_string(idx
++)) != "")
248 cfgfile
<< "BUTTON " << b
<< " " << i
->get_command() << std::endl
;
250 for(auto i
: button_keys
)
251 cfgfile
<< "BUTTON " << i
.second
<< " " << i
.first
<< std::endl
;
252 for(auto i
: core_selections
)
254 cfgfile
<< "PREFER " << i
.first
<< " " << i
.second
<< std::endl
;
256 std::ofstream
lsave(get_config_path() + "/" + our_rom_name
+ ".ls");
261 void* eloop_helper(int x
)
263 platform::dummy_event_loop();
267 std::string
get_loaded_movie(const std::vector
<std::string
>& cmdline
)
269 for(auto i
: cmdline
)
270 if(!i
.empty() && i
[0] != '-')
276 wxString
towxstring(const std::string
& str
) throw(std::bad_alloc
)
278 return wxString(str
.c_str(), wxConvUTF8
);
281 std::string
tostdstring(const wxString
& str
) throw(std::bad_alloc
)
283 return std::string(str
.mb_str(wxConvUTF8
));
286 wxString
towxstring(const std::u32string
& str
) throw(std::bad_alloc
)
288 return wxString(to_u8string(str
).c_str(), wxConvUTF8
);
291 std::u32string
tou32string(const wxString
& str
) throw(std::bad_alloc
)
293 return to_u32string(std::string(str
.mb_str(wxConvUTF8
)));
296 std::string
pick_archive_member(wxWindow
* parent
, const std::string
& filename
) throw(std::bad_alloc
)
298 //Did we pick a .zip file?
301 zip_reader
zr(filename
);
302 std::vector
<wxString
> files
;
304 files
.push_back(towxstring(i
));
305 wxSingleChoiceDialog
* d2
= new wxSingleChoiceDialog(parent
, wxT("Select file within .zip"),
306 wxT("Select member"), files
.size(), &files
[0]);
307 if(d2
->ShowModal() == wxID_CANCEL
) {
311 f
= filename
+ "/" + tostdstring(d2
->GetStringSelection());
320 void signal_program_exit()
322 post_ui_event(UISERV_EXIT
);
325 void signal_resize_needed()
327 post_ui_event(UISERV_RESIZED
);
331 static const wxCmdLineEntryDesc dummy_descriptor_table
[] = {
332 { wxCMD_LINE_PARAM
, NULL
, NULL
, NULL
, wxCMD_LINE_VAL_STRING
, wxCMD_LINE_PARAM_OPTIONAL
|
333 wxCMD_LINE_PARAM_MULTIPLE
},
337 class lsnes_app
: public wxApp
341 virtual bool OnInit();
342 virtual int OnExit();
343 virtual void OnInitCmdLine(wxCmdLineParser
& parser
);
344 virtual bool OnCmdLineParsed(wxCmdLineParser
& parser
);
349 std::vector
<std::string
> cmdline
;
350 std::map
<std::string
, std::string
> c_settings
;
351 std::vector
<std::string
> c_lua
;
352 bool exit_immediately
;
355 IMPLEMENT_APP(lsnes_app
)
357 lsnes_app::lsnes_app()
359 settings_mode
= false;
360 exit_immediately
= false;
363 void lsnes_app::OnInitCmdLine(wxCmdLineParser
& parser
)
365 parser
.SetDesc(dummy_descriptor_table
);
366 parser
.SetSwitchChars(wxT(""));
369 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser
& parser
)
371 for(size_t i
= 0; i
< parser
.GetParamCount(); i
++)
372 cmdline
.push_back(tostdstring(parser
.GetParam(i
)));
373 for(auto i
: cmdline
) {
375 if(i
== "--help" || i
== "-h") {
376 std::cout
<< "--settings: Show the settings dialog" << std::endl
;
377 std::cout
<< "--rom=<filename>: Load specified ROM on startup" << std::endl
;
378 std::cout
<< "--load=<filename>: Load specified save/movie on starup" << std::endl
;
379 std::cout
<< "--lua=<filename>: Load specified Lua script on startup" << std::endl
;
380 std::cout
<< "--set=<a>=<b>: Set setting <a> to value <b>" << std::endl
;
381 std::cout
<< "<filename>: Load specified ROM on startup" << std::endl
;
382 exit_immediately
= true;
385 if(i
== "--settings")
386 settings_mode
= true;
387 if(r
= regex("--set=([^=]+)=(.+)", i
))
388 c_settings
[r
[1]] = r
[2];
389 if(r
= regex("--lua=(.+)", i
))
390 c_lua
.push_back(r
[1]);
396 bool lsnes_app::OnInit()
404 bring_app_foreground();
406 ui_services
= new ui_services_type();
408 ui_thread
= this_thread_id();
411 messages
<< "lsnes version: lsnes rr" << lsnes_version
<< std::endl
;
413 loaded_rom dummy_rom
;
414 std::map
<std::string
, std::string
> settings
;
415 auto ctrldata
= dummy_rom
.rtype
->controllerconfig(settings
);
416 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex());
418 reinitialize_buttonmap();
419 controls
.set_ports(ports
);
421 std::string cfgpath
= get_config_path();
422 autoload_libraries();
423 messages
<< "Saving per-user data to: " << get_config_path() << std::endl
;
424 messages
<< "--- Loading configuration --- " << std::endl
;
425 load_configuration();
426 messages
<< "--- End running lsnesrc --- " << std::endl
;
429 //We got to boot this up quite a bit to get the joystick driver working.
430 //In practicular, we need joystick thread and emulator thread in pause.
431 joystick_thread_handle
= new thread_class(joystick_thread
, 6);
432 thread_class
* dummy_loop
= new thread_class(eloop_helper
, 8);
433 display_settings_dialog(NULL
, NULL
);
434 platform::exit_dummy_event_loop();
435 joystick_driver_signal();
436 joystick_thread_handle
->join();
438 save_configuration();
443 joystick_thread_handle
= new thread_class(joystick_thread
, 7);
445 msg_window
= new wxwin_messages();
448 const std::string movie_file
= get_loaded_movie(cmdline
);
452 rom
= construct_rom(movie_file
, cmdline
);
453 rom
.load(c_settings
, mov
.movie_rtc_second
, mov
.movie_rtc_subsecond
);
454 } catch(std::exception
& e
) {
455 std::cerr
<< "Can't load ROM: " << e
.what() << std::endl
;
459 moviefile
* mov
= NULL
;
462 mov
= new moviefile(movie_file
, *rom
.rtype
);
463 rom
.load(mov
->settings
, mov
->movie_rtc_second
, mov
->movie_rtc_subsecond
);
464 } catch(std::exception
& e
) {
465 std::cerr
<< "Can't load state: " << e
.what() << std::endl
;
470 mov
->settings
= c_settings
;
471 auto ctrldata
= rom
.rtype
->controllerconfig(mov
->settings
);
472 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex());
473 mov
->input
.clear(ports
);
474 mov
->coreversion
= rom
.rtype
->get_core_identifier();
475 mov
->projectid
= get_random_hexstring(40);
476 if(!rom
.rtype
->isnull()) {
477 //Initialize the remainder.
478 mov
->rerecords
= "0";
479 for(size_t i
= 0; i
< ROM_SLOT_COUNT
; i
++) {
480 mov
->romimg_sha256
[i
] = rom
.romimg
[i
].sha_256
.read();
481 mov
->romxml_sha256
[i
] = rom
.romxml
[i
].sha_256
.read();
482 mov
->namehint
[i
] = rom
.romimg
[i
].namehint
;
485 mov
->gametype
= &rom
.rtype
->combine_region(*rom
.region
);
488 mov
->start_paused
= true;
489 for(auto i
: c_lua
) {
490 messages
<< "Trying to run Lua script: " << i
<< std::endl
;
491 lsnes_cmd
.invoke("run-lua " + i
);
493 boot_emulator(rom
, *mov
);
497 int lsnes_app::OnExit()
501 //NULL these so no further messages will be sent.
507 save_configuration();
508 information_dispatch::do_dump_end();
511 joystick_driver_signal();
512 joystick_thread_handle
->join();
521 struct _graphics_driver drv
= {
522 .init
= []() -> void {
523 initialize_wx_keyboard();
525 .quit
= []() -> void {},
526 .notify_message
= []() -> void
528 post_ui_event(UISERV_UPDATE_MESSAGES
);
530 .notify_status
= []() -> void
532 post_ui_event(UISERV_UPDATE_STATUS
);
534 .notify_screen
= []() -> void
536 post_ui_event(UISERV_UPDATE_SCREEN
);
538 .error_message
= [](const std::string
& text
) -> void {
539 error_message_text
= text
;
540 post_ui_event(UISERV_ERROR
);
542 .fatal_error
= []() -> void {
543 //Fun: This can be called from any thread!
544 if(ui_thread
== this_thread_id()) {
546 platform::set_modal_pause(true);
547 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"),
548 wxICON_ERROR
| wxOK
);
550 //Emulation thread panic. Signal the UI thread.
551 post_ui_event(UISERV_PANIC
);
555 .name
= []() -> const char* { return "wxwidgets graphics plugin"; },
556 .action_updated
= []()
558 runuifun([]() -> void { main_window
->action_updated(); });
560 .request_rom
= [](rom_request
& req
)
562 rom_request
* _req
= &req
;
566 umutex_class
h(lock
);
567 runuifun([_req
, &lock
, &cv
, &done
]() -> void {
569 main_window
->request_rom(*_req
);
571 _req
->canceled
= true;
573 umutex_class
h(lock
);
581 struct graphics_driver
_drv(drv
);
584 void signal_core_change()
586 post_ui_event(UISERV_REFRESH_TITLE
);
589 void _runuifun_async(void (*fn
)(void*), void* arg
)
591 umutex_class
h(ui_mutex
);
595 ui_queue
.push_back(e
);
596 post_ui_event(UISERV_UIFUN
);
600 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
602 std::string
pick_file(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
)
604 wxString _title
= towxstring(title
);
605 wxString _startdir
= towxstring(startdir
);
606 std::string filespec
;
607 filespec
= "All files|*";
608 wxFileDialog
* d
= new wxFileDialog(parent
, _title
, _startdir
, wxT(""), towxstring(filespec
), wxFD_OPEN
);
609 if(d
->ShowModal() == wxID_CANCEL
)
610 throw canceled_exception();
611 std::string filename
= tostdstring(d
->GetPath());
614 throw canceled_exception();
618 std::string
pick_file_member(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
)
620 std::string filename
= pick_file(parent
, title
, startdir
);
621 //Did we pick a .zip file?
622 if(!regex_match(".*\\.[zZ][iI][pP]", filename
))
623 return filename
; //Not a ZIP.
625 zip_reader
zr(filename
);
626 std::vector
<std::string
> files
;
629 filename
= filename
+ "/" + pick_among(parent
, "Select member", "Select file within .zip", files
);
630 } catch(canceled_exception
& e
) {
631 //Throw these forward.
639 std::string
pick_among(wxWindow
* parent
, const std::string
& title
, const std::string
& prompt
,
640 const std::vector
<std::string
>& choices
, unsigned defaultchoice
)
642 std::vector
<wxString
> _choices
;
643 for(auto i
: choices
)
644 _choices
.push_back(towxstring(i
));
645 wxSingleChoiceDialog
* d2
= new wxSingleChoiceDialog(parent
, towxstring(prompt
), towxstring(title
),
646 _choices
.size(), &_choices
[0]);
647 d2
->SetSelection(defaultchoice
);
648 if(d2
->ShowModal() == wxID_CANCEL
) {
650 throw canceled_exception();
652 std::string out
= tostdstring(d2
->GetStringSelection());
657 std::string
pick_text(wxWindow
* parent
, const std::string
& title
, const std::string
& prompt
, const std::string
& dflt
,
660 wxTextEntryDialog
* d2
= new wxTextEntryDialog(parent
, towxstring(prompt
), towxstring(title
), towxstring(dflt
),
661 wxOK
| wxCANCEL
| wxCENTRE
| (multiline
? wxTE_MULTILINE
: 0));
662 if(d2
->ShowModal() == wxID_CANCEL
) {
664 throw canceled_exception();
666 std::string text
= tostdstring(d2
->GetValue());
671 void show_message_ok(wxWindow
* parent
, const std::string
& title
, const std::string
& text
, int icon
)
673 wxMessageDialog
* d3
= new wxMessageDialog(parent
, towxstring(text
), towxstring(title
), wxOK
| icon
);