1 //Gaah... wx/wx.h (contains something that breaks if included after snes/snes.hpp from bsnes v085.
5 #include "core/emucore.hpp"
7 #include "core/command.hpp"
8 #include "core/controller.hpp"
9 #include "core/dispatch.hpp"
10 #include "core/framerate.hpp"
11 #include "lua/lua.hpp"
12 #include "core/mainloop.hpp"
13 #include "core/misc.hpp"
14 #include "core/moviedata.hpp"
15 #include "core/rom.hpp"
16 #include "core/rrdata.hpp"
17 #include "core/settings.hpp"
18 #include "core/window.hpp"
19 #include "library/string.hpp"
20 #include "library/zip.hpp"
22 #include "platform/wxwidgets/platform.hpp"
23 #include "platform/wxwidgets/window_messages.hpp"
24 #include "platform/wxwidgets/window_status.hpp"
25 #include "platform/wxwidgets/window_mainwindow.hpp"
28 #include <boost/lexical_cast.hpp>
32 #include <wx/control.h>
33 #include <wx/combobox.h>
34 #include <wx/cmdline.h>
37 #define UISERV_RESIZED 9991
38 #define UISERV_UIFUN 9992
39 //#define UISERV_UI_IRQ 9993 Not in use anymore, can be recycled.
40 #define UISERV_EXIT 9994
41 #define UISERV_UPDATE_STATUS 9995
42 #define UISERV_UPDATE_MESSAGES 9996
43 #define UISERV_UPDATE_SCREEN 9997
44 #define UISERV_PANIC 9998
45 #define UISERV_MODAL 9999
47 wxwin_messages
* msg_window
;
48 wxwin_mainwindow
* main_window
;
49 std::string our_rom_name
;
51 bool dummy_interface
= false;
52 bool wxwidgets_exiting
= false;
57 volatile bool panic_ack
= false;
58 std::string modal_dialog_text
;
59 volatile bool modal_dialog_confirm
;
60 volatile bool modal_dialog_active
;
62 condition
* ui_condition
;
63 thread
* joystick_thread_handle
;
65 void* joystick_thread(void* _args
)
67 joystick_plugin::thread_fn();
70 struct uiserv_event
: public wxEvent
72 uiserv_event(int code
)
77 wxEvent
* Clone() const
79 return new uiserv_event(*this);
83 class ui_services_type
: public wxEvtHandler
85 bool ProcessEvent(wxEvent
& event
);
94 std::list
<ui_queue_entry
> ui_queue
;
96 bool ui_services_type::ProcessEvent(wxEvent
& event
)
98 int c
= event
.GetId();
99 if(c
== UISERV_PANIC
) {
101 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR
|
104 } else if(c
== UISERV_RESIZED
) {
106 main_window
->notify_resized();
107 } else if(c
== UISERV_MODAL
) {
111 mutex::holder
h(*ui_mutex
);
112 text
= modal_dialog_text
;
113 confirm
= modal_dialog_confirm
;
116 int ans
= wxMessageBox(towxstring(text
), _T("Question"), wxICON_QUESTION
| wxOK
|
117 wxCANCEL
, main_window
);
118 confirm
= (ans
== wxOK
);
120 wxMessageBox(towxstring(text
), _T("Notification"), wxICON_INFORMATION
| wxOK
,
124 mutex::holder
h(*ui_mutex
);
125 modal_dialog_confirm
= confirm
;
126 modal_dialog_active
= false;
127 ui_condition
->signal();
129 } else if(c
== UISERV_UPDATE_MESSAGES
) {
131 msg_window
->notify_update();
132 } else if(c
== UISERV_UPDATE_STATUS
) {
134 main_window
->notify_update_status();
135 } else if(c
== UISERV_UPDATE_SCREEN
) {
137 main_window
->notify_update();
138 } else if(c
== UISERV_EXIT
) {
140 main_window
->notify_exit();
141 } else if(c
== UISERV_UIFUN
) {
142 std::list
<ui_queue_entry
>::iterator i
;
143 queue_synchronous_fn_warning
= true;
146 mutex::holder
h(*ui_mutex
);
149 i
= ui_queue
.begin();
153 mutex::holder
h(*ui_mutex
);
158 queue_synchronous_fn_warning
= false;
163 ui_services_type
* ui_services
;
165 void post_ui_event(int code
)
167 uiserv_event
uic(code
);
168 wxPostEvent(ui_services
, uic
);
171 std::map
<std::string
, std::string
> saved_buttons
;
173 void handle_config_line(std::string line
)
176 if(r
= regex("AXIS[ \t]+([^ \t]+)[ \t]+(-?[0-9])[ \t]+(-?[0-9])[ \t]+(-?[0-9])[ \t]+"
177 "(-?[0-9]+)[ \t]+(-?[0-9]+)[ \t]+(-?[0-9]+)[ \t]+(0?.[0-9]+)[ \t]*", line
)) {
178 keyboard_axis_calibration c
;
179 c
.mode
= parse_value
<int>(r
[2]);
180 if(c
.mode
< -1 || c
.mode
> 1) {
181 messages
<< "Illegal axis mode " << c
.mode
<< std::endl
;
184 c
.esign_a
= parse_value
<int>(r
[3]);
185 c
.esign_b
= parse_value
<int>(r
[4]);
186 if(c
.esign_a
< -1 || c
.esign_a
> 1 || c
.esign_b
< -1 || c
.esign_b
> 1 ||
187 c
.esign_a
== c
.esign_b
|| (c
.mode
== 1 && (c
.esign_a
== 0 || c
.esign_b
== 0))) {
188 messages
<< "Illegal axis endings " << c
.esign_a
<< "/" << c
.esign_b
<< std::endl
;
191 c
.left
= parse_value
<int32_t>(r
[5]);
192 c
.center
= parse_value
<int32_t>(r
[6]);
193 c
.right
= parse_value
<int32_t>(r
[7]);
194 c
.nullwidth
= parse_value
<double>(r
[8]);
195 keyboard_key
* _k
= lsnes_kbd
.try_lookup_key(r
[1]);
196 keyboard_key_axis
* k
= NULL
;
201 k
->set_calibration(c
);
202 messages
<< "Calibration of " << r
[1] << " changed: mode=" << calibration_to_mode(c
)
203 << " limits=" << c
.left
<< "(" << c
.center
<< ")" << c
.right
204 << " null=" << c
.nullwidth
<< std::endl
;
205 } else if(r
= regex("UNSET[ \t]+([^ \t]+)[ \t]*", line
)) {
207 lsnes_set
.blank(r
[1]);
208 messages
<< "Setting " << r
[1] << " unset" << std::endl
;
209 } catch(std::exception
& e
) {
210 messages
<< "Can't unset " << r
[1] << ": " << e
.what() << std::endl
;
212 } else if(r
= regex("SET[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
214 lsnes_set
.set(r
[1], r
[2]);
215 messages
<< "Setting " << r
[1] << " set to " << r
[2] << std::endl
;
216 } catch(std::exception
& e
) {
217 messages
<< "Can't set " << r
[1] << ": " << e
.what() << std::endl
;
219 } else if(r
= regex("ALIAS[ \t]+([^ \t]+)[ \t]+(.*)", line
)) {
220 if(!lsnes_cmd
.valid_alias_name(r
[1])) {
221 messages
<< "Illegal alias name " << r
[1] << std::endl
;
224 std::string tmp
= lsnes_cmd
.get_alias_for(r
[1]);
225 tmp
= tmp
+ r
[2] + "\n";
226 lsnes_cmd
.set_alias_for(r
[1], tmp
);
227 messages
<< r
[1] << " aliased to " << r
[2] << std::endl
;
228 } else if(r
= regex("BIND[ \t]+([^/]*)/([^|]*)\\|([^ \t]+)[ \t]+(.*)", line
)) {
229 lsnes_mapper
.bind(r
[1], r
[2], r
[3], r
[4]);
230 if(r
[1] != "" || r
[2] != "")
231 messages
<< r
[1] << "/" << r
[2] << " ";
232 messages
<< r
[3] << " bound to '" << r
[4] << "'" << std::endl
;
233 } else if(r
= regex("BUTTON[ \t]+([^ ]+)[ \t](.*)", line
)) {
234 controller_key
* ckey
= lsnes_mapper
.get_controllerkey(r
[2]);
237 messages
<< r
[1] << " bound (button) to " << r
[2] << std::endl
;
239 saved_buttons
[r
[2]] = r
[1];
241 messages
<< "Unrecognized directive: " << line
<< std::endl
;
244 void load_configuration()
246 std::string cfg
= get_config_path() + "/lsneswxw.cfg";
247 std::ifstream
cfgfile(cfg
.c_str());
249 while(std::getline(cfgfile
, line
))
251 handle_config_line(line
);
252 } catch(std::exception
& e
) {
253 messages
<< "Error processing line: " << e
.what() << std::endl
;
257 void save_configuration()
259 std::string cfg
= get_config_path() + "/lsneswxw.cfg";
260 std::ofstream
cfgfile(cfg
.c_str());
262 for(auto i
: lsnes_kbd
.all_keys()) {
263 keyboard_key_axis
* j
= i
->cast_axis();
266 auto p
= j
->get_calibration();
267 cfgfile
<< "AXIS " << i
->get_name() << " " << p
.mode
<< " " << p
.esign_a
<< " " << p
.esign_b
268 << " " << p
.left
<< " " << p
.center
<< " " << p
.right
<< " " << p
.nullwidth
272 for(auto i
: lsnes_set
.get_settings_set()) {
273 if(!lsnes_set
.is_set(i
))
274 cfgfile
<< "UNSET " << i
<< std::endl
;
276 cfgfile
<< "SET " << i
<< " " << lsnes_set
.get(i
) << std::endl
;
278 for(auto i
: lsnes_set
.get_invalid_values())
279 cfgfile
<< "SET " << i
.first
<< " " << i
.second
<< std::endl
;
281 for(auto i
: lsnes_cmd
.get_aliases()) {
282 std::string old_alias_value
= lsnes_cmd
.get_alias_for(i
);
283 while(old_alias_value
!= "") {
284 std::string aliasline
;
285 size_t s
= old_alias_value
.find_first_of("\n");
286 if(s
< old_alias_value
.length()) {
287 aliasline
= old_alias_value
.substr(0, s
);
288 old_alias_value
= old_alias_value
.substr(s
+ 1);
290 aliasline
= old_alias_value
;
291 old_alias_value
= "";
293 cfgfile
<< "ALIAS " << i
<< " " << aliasline
<< std::endl
;
297 for(auto i
: lsnes_mapper
.get_bindings())
298 cfgfile
<< "BIND " << std::string(i
) << " " << lsnes_mapper
.get(i
) << std::endl
;
300 for(auto i
: lsnes_mapper
.get_controller_keys()) {
301 std::string b
= i
->get_string();
303 cfgfile
<< "BUTTON " << b
<< " " << i
->get_command() << std::endl
;
305 for(auto i
: saved_buttons
)
306 cfgfile
<< "BUTTON " << i
.second
<< " " << i
.first
<< std::endl
;
308 std::ofstream
lsave(get_config_path() + "/" + our_rom_name
+ ".ls");
313 void* eloop_helper(void* x
)
315 platform::dummy_event_loop();
320 wxString
towxstring(const std::string
& str
) throw(std::bad_alloc
)
322 return wxString(str
.c_str(), wxConvUTF8
);
325 std::string
tostdstring(const wxString
& str
) throw(std::bad_alloc
)
327 return std::string(str
.mb_str(wxConvUTF8
));
330 std::string
pick_archive_member(wxWindow
* parent
, const std::string
& filename
) throw(std::bad_alloc
)
332 //Did we pick a .zip file?
335 zip_reader
zr(filename
);
336 std::vector
<wxString
> files
;
338 files
.push_back(towxstring(i
));
339 wxSingleChoiceDialog
* d2
= new wxSingleChoiceDialog(parent
, wxT("Select file within .zip"),
340 wxT("Select member"), files
.size(), &files
[0]);
341 if(d2
->ShowModal() == wxID_CANCEL
) {
345 f
= filename
+ "/" + tostdstring(d2
->GetStringSelection());
354 void signal_program_exit()
356 post_ui_event(UISERV_EXIT
);
359 void signal_resize_needed()
361 post_ui_event(UISERV_RESIZED
);
364 void graphics_plugin::init() throw()
366 initialize_wx_keyboard();
369 void graphics_plugin::quit() throw()
373 static const wxCmdLineEntryDesc dummy_descriptor_table
[] = {
374 { wxCMD_LINE_PARAM
, NULL
, NULL
, NULL
, wxCMD_LINE_VAL_STRING
, wxCMD_LINE_PARAM_OPTIONAL
|
375 wxCMD_LINE_PARAM_MULTIPLE
},
379 class lsnes_app
: public wxApp
383 virtual bool OnInit();
384 virtual int OnExit();
385 virtual void OnInitCmdLine(wxCmdLineParser
& parser
);
386 virtual bool OnCmdLineParsed(wxCmdLineParser
& parser
);
391 std::map
<std::string
, std::string
> c_settings
;
394 IMPLEMENT_APP(lsnes_app
)
396 lsnes_app::lsnes_app()
398 settings_mode
= false;
401 void lsnes_app::OnInitCmdLine(wxCmdLineParser
& parser
)
403 parser
.SetDesc(dummy_descriptor_table
);
404 parser
.SetSwitchChars(wxT(""));
407 bool lsnes_app::OnCmdLineParsed(wxCmdLineParser
& parser
)
409 std::vector
<std::string
> cmdline
;
410 for(size_t i
= 0; i
< parser
.GetParamCount(); i
++)
411 cmdline
.push_back(tostdstring(parser
.GetParam(i
)));
412 for(auto i
: cmdline
) {
414 if(i
== "--settings")
415 settings_mode
= true;
416 if(r
= regex("--rom=(.+)", i
))
418 if(r
= regex("--load=(.+)", i
))
420 if(r
= regex("--set=([^=]+)=(.+)", i
))
421 c_settings
[r
[1]] = r
[2];
426 bool lsnes_app::OnInit()
432 bring_app_foreground();
434 ui_services
= new ui_services_type();
435 ui_mutex
= &mutex::aquire();
436 ui_condition
= &condition::aquire(*ui_mutex
);
438 bsnes_core_version
= emulator_core
->get_core_identifier();
439 ui_thread
= &thread_id::me();
442 messages
<< "BSNES version: " << bsnes_core_version
<< std::endl
;
443 messages
<< "lsnes version: lsnes rr" << lsnes_version
<< std::endl
;
445 loaded_rom dummy_rom
;
446 std::map
<std::string
, std::string
> settings
;
447 auto ctrldata
= dummy_rom
.rtype
->controllerconfig(settings
);
448 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex
);
450 controls
.set_ports(ports
);
452 std::string cfgpath
= get_config_path();
453 messages
<< "Saving per-user data to: " << get_config_path() << std::endl
;
454 messages
<< "--- Loading configuration --- " << std::endl
;
455 lsnes_set
.set_storage_mode(true);
456 load_configuration();
457 lsnes_set
.set_storage_mode(false);
458 messages
<< "--- End running lsnesrc --- " << std::endl
;
461 //We got to boot this up quite a bit to get the joystick driver working.
462 //In practicular, we need joystick thread and emulator thread in pause.
463 joystick_thread_handle
= &thread::create(joystick_thread
, NULL
);
464 thread
* dummy_loop
= &thread::create(eloop_helper
, NULL
);
465 wxsetingsdialog_display(NULL
, false);
466 platform::exit_dummy_event_loop();
467 joystick_plugin::signal();
468 joystick_thread_handle
->join();
470 save_configuration();
475 joystick_thread_handle
= &thread::create(joystick_thread
, NULL
);
477 msg_window
= new wxwin_messages();
480 loaded_rom
* rom
= NULL
;
484 rom
= new loaded_rom(c_rom
);
485 rom
->load(c_settings
, mov
.movie_rtc_second
, mov
.movie_rtc_subsecond
);
486 } catch(std::exception
& e
) {
487 std::cerr
<< "Can't load ROM: " << e
.what() << std::endl
;
491 rom
= new loaded_rom
;
492 moviefile
* mov
= NULL
;
495 mov
= new moviefile(c_file
);
497 rom
->load(mov
->settings
, mov
->movie_rtc_second
, mov
->movie_rtc_subsecond
);
498 } catch(std::exception
& e
) {
499 std::cerr
<< "Can't load state: " << e
.what() << std::endl
;
504 mov
->settings
= c_settings
;
505 auto ctrldata
= rom
->rtype
->controllerconfig(mov
->settings
);
506 port_type_set
& ports
= port_type_set::make(ctrldata
.ports
, ctrldata
.portindex
);
507 mov
->input
.clear(ports
);
509 //Initialize the remainder.
510 mov
->coreversion
= bsnes_core_version
;
511 mov
->projectid
= get_random_hexstring(40);
512 mov
->rerecords
= "0";
513 for(size_t i
= 0; i
< sizeof(rom
->romimg
)/sizeof(rom
->romimg
[0]); i
++) {
514 mov
->romimg_sha256
[i
] = rom
->romimg
[i
].sha_256
;
515 mov
->romxml_sha256
[i
] = rom
->romxml
[i
].sha_256
;
518 mov
->gametype
= &rom
->rtype
->combine_region(*rom
->region
);
520 mov
->start_paused
= true;
521 boot_emulator(*rom
, *mov
);
526 int lsnes_app::OnExit()
530 //NULL these so no further messages will be sent.
532 auto y
= main_window
;
537 save_configuration();
538 information_dispatch::do_dump_end();
541 joystick_plugin::signal();
542 joystick_thread_handle
->join();
547 void graphics_plugin::notify_message() throw()
549 post_ui_event(UISERV_UPDATE_MESSAGES
);
552 void graphics_plugin::notify_status() throw()
554 post_ui_event(UISERV_UPDATE_STATUS
);
557 void graphics_plugin::notify_screen() throw()
559 post_ui_event(UISERV_UPDATE_SCREEN
);
562 bool graphics_plugin::modal_message(const std::string
& text
, bool confirm
) throw()
564 mutex::holder
h(*ui_mutex
);
565 modal_dialog_active
= true;
566 modal_dialog_confirm
= confirm
;
567 modal_dialog_text
= text
;
568 post_ui_event(UISERV_MODAL
);
569 while(modal_dialog_active
)
570 ui_condition
->wait(10000);
571 return modal_dialog_confirm
;
574 void graphics_plugin::fatal_error() throw()
576 //Fun: This can be called from any thread!
577 if(ui_thread
->is_me()) {
579 platform::set_modal_pause(true);
580 wxMessageBox(_T("Panic: Unrecoverable error, can't continue"), _T("Error"), wxICON_ERROR
| wxOK
);
582 //Emulation thread panic. Signal the UI thread.
583 post_ui_event(UISERV_PANIC
);
588 void _runuifun_async(void (*fn
)(void*), void* arg
)
590 mutex::holder
h(*ui_mutex
);
594 ui_queue
.push_back(e
);
595 post_ui_event(UISERV_UIFUN
);
599 canceled_exception::canceled_exception() : std::runtime_error("Dialog canceled") {}
601 std::string
pick_file(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
, bool forsave
)
603 wxString _title
= towxstring(title
);
604 wxString _startdir
= towxstring(startdir
);
605 wxFileDialog
* d
= new wxFileDialog(parent
, _title
, _startdir
, wxT(""), wxT("All files|*"), forsave
?
606 wxFD_SAVE
: wxFD_OPEN
);
607 if(d
->ShowModal() == wxID_CANCEL
)
608 throw canceled_exception();
609 std::string filename
= tostdstring(d
->GetPath());
612 throw canceled_exception();
616 std::string
pick_file_member(wxWindow
* parent
, const std::string
& title
, const std::string
& startdir
)
618 std::string filename
= pick_file(parent
, title
, startdir
, false);
619 //Did we pick a .zip file?
621 zip_reader
zr(filename
);
622 std::vector
<std::string
> files
;
625 filename
= filename
+ "/" + pick_among(parent
, "Select member", "Select file within .zip", files
);
626 } catch(canceled_exception
& e
) {
627 //Throw these forward.
635 std::string
pick_among(wxWindow
* parent
, const std::string
& title
, const std::string
& prompt
,
636 const std::vector
<std::string
>& choices
)
638 std::vector
<wxString
> _choices
;
639 for(auto i
: choices
)
640 _choices
.push_back(towxstring(i
));
641 wxSingleChoiceDialog
* d2
= new wxSingleChoiceDialog(parent
, towxstring(prompt
), towxstring(title
),
642 _choices
.size(), &_choices
[0]);
643 if(d2
->ShowModal() == wxID_CANCEL
) {
645 throw canceled_exception();
647 std::string out
= tostdstring(d2
->GetStringSelection());
652 std::string
pick_text(wxWindow
* parent
, const std::string
& title
, const std::string
& prompt
, const std::string
& dflt
,
655 wxTextEntryDialog
* d2
= new wxTextEntryDialog(parent
, towxstring(prompt
), towxstring(title
), towxstring(dflt
),
656 wxOK
| wxCANCEL
| wxCENTRE
| (multiline
? wxTE_MULTILINE
: 0));
657 if(d2
->ShowModal() == wxID_CANCEL
) {
659 throw canceled_exception();
661 std::string text
= tostdstring(d2
->GetValue());
666 void show_message_ok(wxWindow
* parent
, const std::string
& title
, const std::string
& text
, int icon
)
668 wxMessageDialog
* d3
= new wxMessageDialog(parent
, towxstring(text
), towxstring(title
), wxOK
| icon
);
674 const char* graphics_plugin::name
= "wxwidgets graphics plugin";