diff --git a/include/core/advdumper.hpp b/include/core/advdumper.hpp index b9888032..a06574fd 100644 --- a/include/core/advdumper.hpp +++ b/include/core/advdumper.hpp @@ -10,6 +10,13 @@ class adv_dumper { public: +/** + * Detail flags. + */ + static unsigned target_type_mask; + static unsigned target_type_file; + static unsigned target_type_prefix; + static unsigned target_type_special; /** * Register a dumper. * @@ -42,11 +49,12 @@ public: */ virtual std::set list_submodes() throw(std::bad_alloc) = 0; /** - * Does this dumper want a prefix? + * Get mode details * * parameter mode: The submode. + * Returns: Mode details flags */ - virtual bool wants_prefix(const std::string& mode) throw() = 0; + virtual unsigned mode_details(const std::string& mode) throw() = 0; /** * Get human-readable name for this dumper. * diff --git a/include/platform/wxwidgets/platform.hpp b/include/platform/wxwidgets/platform.hpp index f6597425..6241aea9 100644 --- a/include/platform/wxwidgets/platform.hpp +++ b/include/platform/wxwidgets/platform.hpp @@ -16,6 +16,11 @@ class wxwin_status; class wxWindow; class wxKeyEvent; +//Scaling +extern double horizontal_scale_factor; +extern double vertical_scale_factor; +extern int scaling_flags; + wxString towxstring(const std::string& str) throw(std::bad_alloc); std::string tostdstring(const wxString& str) throw(std::bad_alloc); void bring_app_foreground(); @@ -28,13 +33,12 @@ void signal_resize_needed(); void _runuifun_async(void (*fn)(void*), void* arg); //Editor dialogs. -void wxeditor_axes_display(wxWindow* parent); void wxeditor_authors_display(wxWindow* parent); void wxeditor_settings_display(wxWindow* parent); void wxeditor_hotkeys_display(wxWindow* parent); -void wxeditor_paths_display(wxWindow* parent); std::string wxeditor_keyselect(wxWindow* parent, bool clearable); -void wxeditor_screen_display(wxWindow* parent, double& horiz, double& vert, int& flags); +void wxeditor_screen_display(wxWindow* parent); +void wxsetingsdialog_display(wxWindow* parent); //Auxillary windows. void wxwindow_memorysearch_display(); diff --git a/include/video/avi/codec.hpp b/include/video/avi/codec.hpp index 1fd79bd2..8abc3c5e 100644 --- a/include/video/avi/codec.hpp +++ b/include/video/avi/codec.hpp @@ -69,6 +69,8 @@ struct avi_video_codec * Parameter fps_n: fps numerator. * Parameter fps_d: fps denominator. * Returns: Stream format. + * + * Note: The next frame emitted MUST be a keyframe. */ virtual format reset(uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d) = 0; /** diff --git a/include/video/tcp.hpp b/include/video/tcp.hpp new file mode 100644 index 00000000..25dac522 --- /dev/null +++ b/include/video/tcp.hpp @@ -0,0 +1,26 @@ +#ifndef _tcp__hpp__included__ +#define _tcp__hpp__included__ + +#include +#include +#include + +typedef void (*deleter_fn_t)(void*); + +class socket_address +{ +public: + socket_address(const std::string& spec); + socket_address next(); + std::ostream& connect(); + static deleter_fn_t deleter(); + static bool supported(); +private: + socket_address(int f, int st, int p); + int family; + int socktype; + int protocol; + std::vector memory; +}; + +#endif diff --git a/src/core/advdumper.cpp b/src/core/advdumper.cpp index 46836587..90c7446e 100644 --- a/src/core/advdumper.cpp +++ b/src/core/advdumper.cpp @@ -66,12 +66,22 @@ namespace return; } for(auto j : b) { - if(i->wants_prefix(j)) + unsigned d = i->mode_details(j); + if((d & adv_dumper::target_type_mask) == + adv_dumper::target_type_prefix) messages << "P " << x << "\t" << j << "\t" << i->modename(j) << std::endl; - else + else if((d & adv_dumper::target_type_mask) == + adv_dumper::target_type_file) messages << "F " << x << "\t" << j << "\t" << i->modename(j) << std::endl; + else if((d & adv_dumper::target_type_mask) == + adv_dumper::target_type_special) + messages << "S " << x << "\t" << j << "\t" + << i->modename(j) << std::endl; + else + messages << "U " << x << "\t" << j << "\t" + << i->modename(j) << std::endl; } return; } @@ -106,6 +116,11 @@ adv_dumper::adv_dumper(const std::string& id) throw(std::bad_alloc) dumpers()[d_id] = this; } +unsigned adv_dumper::target_type_mask = 3; +unsigned adv_dumper::target_type_file = 0; +unsigned adv_dumper::target_type_prefix = 1; +unsigned adv_dumper::target_type_special = 2; + template void render_video_hud(struct screen& target, struct lcscreen& source, uint32_t hscl, uint32_t vscl, uint32_t roffset, uint32_t goffset, uint32_t boffset, uint32_t lgap, uint32_t tgap, uint32_t rgap, uint32_t bgap, void(*fn)()) diff --git a/src/platform/Makefile b/src/platform/Makefile index 4a49a901..7ac6435a 100644 --- a/src/platform/Makefile +++ b/src/platform/Makefile @@ -1,4 +1,4 @@ -PLATFORMS=dummy evdev portaudio sdl wxwidgets +PLATFORMS=dummy evdev portaudio sdl wxwidgets win32mm ALLOBJECT=__all__.$(OBJECT_SUFFIX) ALLFLAGS=__all__.ldflags PLATFORMS_OBJS=$(patsubst %,%/$(ALLOBJECT),$(PLATFORMS)) @@ -14,6 +14,9 @@ dummy/$(ALLOBJECT): forcelook evdev/$(ALLOBJECT): forcelook $(MAKE) -C evdev +win32mm/$(ALLOBJECT): forcelook + $(MAKE) -C win32mm + portaudio/$(ALLOBJECT): forcelook $(MAKE) -C portaudio @@ -29,6 +32,7 @@ wxwidgets/$(ALLOBJECT): forcelook precheck: $(MAKE) -C dummy precheck $(MAKE) -C evdev precheck + $(MAKE) -C win32mm precheck $(MAKE) -C portaudio precheck $(MAKE) -C sdl precheck $(MAKE) -C wxwidgets precheck @@ -37,6 +41,7 @@ clean: rm -f *.$(OBJECT_SUFFIX) *.ldflags $(MAKE) -C dummy clean $(MAKE) -C evdev clean + $(MAKE) -C win32mm clean $(MAKE) -C portaudio clean $(MAKE) -C sdl clean $(MAKE) -C wxwidgets clean diff --git a/src/platform/win32mm/Makefile b/src/platform/win32mm/Makefile new file mode 100644 index 00000000..5705fbf9 --- /dev/null +++ b/src/platform/win32mm/Makefile @@ -0,0 +1,20 @@ +ifeq ($(JOYSTICK), WIN32MM) +OBJECTS=$(patsubst %.cpp,%.$(OBJECT_SUFFIX),$(wildcard *.cpp)) +else +OBJECTS=dummy.$(OBJECT_SUFFIX) +endif + +.PRECIOUS: %.$(OBJECT_SUFFIX) + +__all__.$(OBJECT_SUFFIX): $(OBJECTS) + $(REALLD) -r -o $@ $^ + touch __all__.ldflags + +%.$(OBJECT_SUFFIX): %.cpp + $(REALCC) -c -o $@ $< -I../../../include $(CFLAGS) + +precheck: + @true + +clean: + rm -f *.$(OBJECT_SUFFIX) *.ldflags diff --git a/src/platform/win32mm/dummy.cpp b/src/platform/win32mm/dummy.cpp new file mode 100644 index 00000000..8f4c3306 --- /dev/null +++ b/src/platform/win32mm/dummy.cpp @@ -0,0 +1 @@ +char SYMBOL_5825392786327896327896798437692738969832786; diff --git a/src/platform/win32mm/joystick.cpp b/src/platform/win32mm/joystick.cpp new file mode 100644 index 00000000..67f44606 --- /dev/null +++ b/src/platform/win32mm/joystick.cpp @@ -0,0 +1,216 @@ +#include "core/command.hpp" +#include "core/framerate.hpp" +#include "core/keymapper.hpp" +#include "core/window.hpp" +#include "library/minmax.hpp" +#include "library/string.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + std::set keygroups; + std::map, keygroup*> buttons; + std::map, keygroup*> axes; + std::map hats; + std::map, short> lbuttons; + std::map, short> laxes; + std::map lhats; + std::set joysticks; + std::map capabilities; + volatile bool quit_signaled; + volatile bool quit_ack; + + void create_hat(unsigned i) + { + std::string n = (stringfmt() << "joystick" << i << "hat").str(); + keygroup* k = new keygroup(n, keygroup::KT_HAT); + hats[i] = k; + } + + void create_button(unsigned i, unsigned j) + { + std::string n = (stringfmt() << "joystick" << i << "button" << j).str(); + keygroup* k = new keygroup(n, keygroup::KT_KEY); + buttons[std::make_pair(i, j)] = k; + } + + void create_axis(unsigned i, unsigned j, unsigned min, unsigned max) + { + std::string n = (stringfmt() << "joystick" << i << "axis" << j).str(); + keygroup* k; + k = new keygroup(n, keygroup::KT_AXIS_PAIR); + axes[std::make_pair(i, j)] = k; + } + + void read_axis(unsigned i, unsigned j, unsigned pos, unsigned pmin, unsigned pmax) + { + auto key = std::make_pair(i, j); + if(!axes.count(key)) + return; + short cpos; + double _pos = pos; + double _pmin = pmin; + double _pmax = pmax; + _pos = 65535 * (_pos - _pmin) / (_pmax - _pmin) - 32768; + if(_pos < -32768) + cpos = -32768; + else if(_pos > 32767) + cpos = 32767; + else + cpos = _pos; + if(laxes[key] != cpos) { + platform::queue(keypress(modifier_set(), *axes[key], cpos)); + laxes[key] = cpos; + } + } + + void init_joysticks() + { + unsigned max_joysticks = joyGetNumDevs(); + if(!max_joysticks) + return; //No joystick support. + for(unsigned i = 0; i < max_joysticks; i++) { + JOYINFOEX info; + JOYCAPS caps; + info.dwSize = sizeof(info); + info.dwFlags = JOY_RETURNALL; + if(joyGetPosEx(i, &info) != JOYERR_NOERROR) + continue; //Not usable. + if(joyGetDevCaps(i, &caps, sizeof(caps)) != JOYERR_NOERROR) + continue; //Not usable. + joysticks.insert(i); + capabilities[i] = caps; + messages << "Joystick #" << i << ": " << caps.szPname << " (by '" << caps.szOEMVxD << "')" + << std::endl; + if(caps.wCaps & JOYCAPS_HASPOV) + create_hat(i); + for(unsigned j = 0; j < caps.wNumButtons && j < 32; j++) + create_button(i, j); + unsigned axcount = 2; + create_axis(i, 0, caps.wXmin, caps.wXmax); + create_axis(i, 1, caps.wYmin, caps.wYmax); + if(caps.wCaps & JOYCAPS_HASZ) { + create_axis(i, 2, caps.wZmin, caps.wZmax); + axcount++; + } + if(caps.wCaps & JOYCAPS_HASR) { + create_axis(i, 3, caps.wRmin, caps.wRmax); + axcount++; + } + if(caps.wCaps & JOYCAPS_HASU) { + create_axis(i, 4, caps.wUmin, caps.wUmax); + axcount++; + } + if(caps.wCaps & JOYCAPS_HASV) { + create_axis(i, 5, caps.wVmin, caps.wVmax); + axcount++; + } + if(caps.wCaps & JOYCAPS_HASPOV) + messages << "1 hat, "; + messages << axcount << " axes, " << min((int)caps.wNumButtons, 32) << " buttons" << std::endl; + } + } + + void quit_joysticks() + { + for(auto i : keygroups) + delete i; + buttons.clear(); + axes.clear(); + hats.clear(); + keygroups.clear(); + joysticks.clear(); + capabilities.clear(); + } + + void poll_joysticks() + { + modifier_set mod; + for(auto i : capabilities) { + unsigned jnum = i.first; + JOYINFOEX info; + JOYCAPS caps = capabilities[jnum]; + info.dwSize = sizeof(info); + info.dwFlags = JOY_RETURNALL; + if(joyGetPosEx(jnum, &info) != JOYERR_NOERROR) + continue; //Not usable. + if(caps.wCaps & JOYCAPS_HASPOV) { + //Read POV hat. + short m = 0; + int pov = info.dwPOV; + if((pov >= 0 && pov <= 6000) || (pov >= 30000 && pov <= 36000)) + m |= 1; + if(pov >= 3000 && pov <= 15000) + m |= 2; + if(pov >= 12000 && pov <= 24000) + m |= 4; + if(pov >= 21000 && pov <= 33000) + m |= 8; + if(lhats[jnum] != m) { + platform::queue(keypress(modifier_set(), *hats[jnum], m)); + lhats[jnum] = m; + } + } + for(unsigned j = 0; j < caps.wMaxButtons; j++) { + //Read buttons + auto key = std::make_pair(jnum, j); + short x = (info.dwButtons >> j) & 1; + if(buttons.count(key) && lbuttons[key] != x) { + platform::queue(keypress(modifier_set(), *buttons[key], x)); + lbuttons[key] = x; + } + } + read_axis(jnum, 0, info.dwXpos, caps.wXmin, caps.wXmax); + read_axis(jnum, 1, info.dwYpos, caps.wYmin, caps.wYmax); + if(caps.wCaps & JOYCAPS_HASZ) + read_axis(jnum, 2, info.dwZpos, caps.wZmin, caps.wZmax); + if(caps.wCaps & JOYCAPS_HASR) + read_axis(jnum, 3, info.dwRpos, caps.wRmin, caps.wRmax); + if(caps.wCaps & JOYCAPS_HASU) + read_axis(jnum, 4, info.dwUpos, caps.wUmin, caps.wUmax); + if(caps.wCaps & JOYCAPS_HASV) + read_axis(jnum, 5, info.dwVpos, caps.wVmin, caps.wVmax); + } + } +} + +void joystick_plugin::init() throw() +{ + init_joysticks(); + quit_ack = quit_signaled = false; +} + +void joystick_plugin::quit() throw() +{ + quit_signaled = true; + while(!quit_ack); + quit_joysticks(); +} + +#define POLL_WAIT 20000 + +void joystick_plugin::thread_fn() throw() +{ + while(!quit_signaled) { + poll_joysticks(); + usleep(POLL_WAIT); + } + quit_ack = true; +} + +void joystick_plugin::signal() throw() +{ + quit_signaled = true; +} + +const char* joystick_plugin::name = "Win32mm joystick plugin"; diff --git a/src/platform/wxwidgets/dumpmenu.cpp b/src/platform/wxwidgets/dumpmenu.cpp index a78a63b4..53fbff54 100644 --- a/src/platform/wxwidgets/dumpmenu.cpp +++ b/src/platform/wxwidgets/dumpmenu.cpp @@ -96,25 +96,58 @@ void dumper_menu::on_select(wxCommandEvent& e) if(id < wxid_range_low || id > wxid_range_high) return; for(auto i : menustructure) { + std::string error_str; adv_dumper* t = existing_dumpers[i.first].instance; if(i.second.end_wxid == id) { //Execute end of dump operation. - runemufn([t]() { t->end(); }); + runemufn([t, &error_str]() { + try { + t->end(); + } catch(std::exception& e) { + error_str = e.what(); + }}); + if(error_str != "") + wxMessageBox(towxstring(error_str), _T("Error ending dump"), wxICON_EXCLAMATION | wxOK, + pwin); return; } if(i.second.start_wxids.count(id)) { //Execute start of dump operation. std::string mode = i.second.start_wxids[id]; - bool prefixed = t->wants_prefix(mode); + unsigned d = t->mode_details(mode); std::string prefix; - wxFileDialog* d = new wxFileDialog(pwin, towxstring(prefixed ? std::string("Choose prefix") : - std::string("Choose file")), wxT(".")); - if(d->ShowModal() == wxID_OK) - prefix = tostdstring(d->GetPath()); - d->Destroy(); + if((d & adv_dumper::target_type_mask) == adv_dumper::target_type_file) { + wxFileDialog* d = new wxFileDialog(pwin, wxT("Choose file"), wxT(".")); + if(d->ShowModal() == wxID_OK) + prefix = tostdstring(d->GetPath()); + d->Destroy(); + } else if((d & adv_dumper::target_type_mask) == adv_dumper::target_type_prefix) { + wxFileDialog* d = new wxFileDialog(pwin, wxT("Choose prefix"), wxT(".")); + if(d->ShowModal() == wxID_OK) + prefix = tostdstring(d->GetPath()); + d->Destroy(); + } else if((d & adv_dumper::target_type_mask) == adv_dumper::target_type_special) { + try { + prefix = pick_text(pwin, "Choose target", "Enter target to dump to", ""); + } catch(...) { + return; + } + } else { + wxMessageBox(wxT("Unsupported target type"), _T("Dumper error"), wxICON_EXCLAMATION | + wxOK, pwin); + return; + } if(prefix == "") return; - runemufn([t, mode, prefix]() { t->start(mode, prefix); }); + runemufn([t, mode, prefix, &error_str]() { + try { + t->start(mode, prefix); + } catch(std::exception& e) { + error_str = e.what(); + }}); + if(error_str != "") + wxMessageBox(towxstring(error_str), _T("Error starting dump"), wxICON_EXCLAMATION | + wxOK, pwin); return; } } diff --git a/src/platform/wxwidgets/editor-axis.cpp b/src/platform/wxwidgets/editor-axis.cpp deleted file mode 100644 index 54eb63f1..00000000 --- a/src/platform/wxwidgets/editor-axis.cpp +++ /dev/null @@ -1,292 +0,0 @@ -#include "core/keymapper.hpp" - -#include "platform/wxwidgets/platform.hpp" - -#include -#include - -#define AMODE_DISABLED "Disabled" -#define AMODE_AXIS_PAIR "Axis" -#define AMODE_AXIS_PAIR_INVERSE "Axis (inverted)" -#define AMODE_PRESSURE_M0 "Pressure - to 0" -#define AMODE_PRESSURE_MP "Pressure - to +" -#define AMODE_PRESSURE_0M "Pressure 0 to -" -#define AMODE_PRESSURE_0P "Pressure 0 to +" -#define AMODE_PRESSURE_PM "Pressure + to -" -#define AMODE_PRESSURE_P0 "Pressure + to 0" - -#include -#include -#include -#include -#include -#include - -class wxeditor_axes_axis -{ -public: - wxeditor_axes_axis(wxSizer* sizer, wxWindow* window, const std::string& name); - bool is_ok(); - void apply(); -private: - std::string a_name; - wxComboBox* a_type; - wxTextCtrl* a_low; - wxTextCtrl* a_mid; - wxTextCtrl* a_high; - wxTextCtrl* a_tolerance; -}; - -class wxeditor_axes : public wxDialog -{ -public: - wxeditor_axes(wxWindow* parent); - ~wxeditor_axes(); - bool ShouldPreventAppExit() const; - void on_value_change(wxCommandEvent& e); - void on_cancel(wxCommandEvent& e); - void on_ok(wxCommandEvent& e); - bool has_axes(); -private: - std::vector axes; - wxButton* okbutton; - wxButton* cancel; -}; - -//Should be called in modal pause mode. -wxeditor_axes_axis::wxeditor_axes_axis(wxSizer* sizer, wxWindow* window, const std::string& name) -{ - wxString choices[9]; - choices[0] = wxT(AMODE_DISABLED); - choices[1] = wxT(AMODE_AXIS_PAIR); - choices[2] = wxT(AMODE_AXIS_PAIR_INVERSE); - choices[3] = wxT(AMODE_PRESSURE_M0); - choices[4] = wxT(AMODE_PRESSURE_MP); - choices[5] = wxT(AMODE_PRESSURE_0M); - choices[6] = wxT(AMODE_PRESSURE_0P); - choices[7] = wxT(AMODE_PRESSURE_PM); - choices[8] = wxT(AMODE_PRESSURE_P0); - size_t defaultidx = 0; - std::string low; - std::string mid; - std::string high; - std::string tolerance; - keygroup* k; - runemufn([&k, name]() { k = keygroup::lookup_by_name(name); }); - if(!k) { - return; - } - struct keygroup::parameters p = k->get_parameters(); - { - switch(p.ktype) { - case keygroup::KT_DISABLED: defaultidx = 0; break; - case keygroup::KT_AXIS_PAIR: defaultidx = 1; break; - case keygroup::KT_AXIS_PAIR_INVERSE: defaultidx = 2; break; - case keygroup::KT_PRESSURE_M0: defaultidx = 3; break; - case keygroup::KT_PRESSURE_MP: defaultidx = 4; break; - case keygroup::KT_PRESSURE_0M: defaultidx = 5; break; - case keygroup::KT_PRESSURE_0P: defaultidx = 6; break; - case keygroup::KT_PRESSURE_PM: defaultidx = 7; break; - case keygroup::KT_PRESSURE_P0: defaultidx = 8; break; - }; - std::ostringstream x1; - std::ostringstream x2; - std::ostringstream x3; - std::ostringstream x4; - x1 << p.cal_left; - x2 << p.cal_center; - x3 << p.cal_right; - x4 << p.cal_tolerance; - low = x1.str(); - mid = x2.str(); - high = x3.str(); - tolerance = x4.str(); - } - - a_name = name; - sizer->Add(new wxStaticText(window, wxID_ANY, towxstring(name)), 0, wxGROW); - sizer->Add(a_type = new wxComboBox(window, wxID_ANY, choices[defaultidx], wxDefaultPosition, wxDefaultSize, - 9, choices, wxCB_READONLY), 0, wxGROW); - sizer->Add(a_low = new wxTextCtrl(window, wxID_ANY, towxstring(low)), 0, wxGROW); - sizer->Add(a_mid = new wxTextCtrl(window, wxID_ANY, towxstring(mid)), 0, wxGROW); - sizer->Add(a_high = new wxTextCtrl(window, wxID_ANY, towxstring(high)), 0, wxGROW); - sizer->Add(a_tolerance = new wxTextCtrl(window, wxID_ANY, towxstring(tolerance)), 0, wxGROW); - a_low->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_axes::on_value_change), NULL, - window); - a_mid->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_axes::on_value_change), NULL, - window); - a_high->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_axes::on_value_change), NULL, - window); - a_tolerance->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(wxeditor_axes::on_value_change), NULL, - window); -} - -bool wxeditor_axes_axis::is_ok() -{ - int32_t low, mid, high; - double tolerance; - - try { - low = boost::lexical_cast(tostdstring(a_low->GetValue())); - mid = boost::lexical_cast(tostdstring(a_mid->GetValue())); - high = boost::lexical_cast(tostdstring(a_high->GetValue())); - tolerance = boost::lexical_cast(tostdstring(a_tolerance->GetValue())); - } catch(...) { - return false; - } - - if(low < -32768 || low > 32767 || low > mid) - return false; - if(mid < -32768 || mid > 32767 || mid > high) - return false; - if(high < -32768 || high > 32767) - return false; - if(tolerance <= 0 || tolerance >= 1) - return false; - return true; -} - -//Should be called in modal pause mode. -void wxeditor_axes_axis::apply() -{ - keygroup* k; - runemufn([&k, a_name]() { k = keygroup::lookup_by_name(a_name); }); - if(!k) - return; - - int32_t low, mid, high; - double tolerance; - enum keygroup::type ntype; - enum keygroup::type ctype; - runemufn([&ctype, k]() { ctype = k->get_parameters().ktype; }); - - std::string amode = tostdstring(a_type->GetValue()); - if(amode == AMODE_AXIS_PAIR) - ntype = keygroup::KT_AXIS_PAIR; - if(amode == AMODE_AXIS_PAIR_INVERSE) - ntype = keygroup::KT_AXIS_PAIR_INVERSE; - if(amode == AMODE_DISABLED) - ntype = keygroup::KT_DISABLED; - if(amode == AMODE_PRESSURE_0M) - ntype = keygroup::KT_PRESSURE_0M; - if(amode == AMODE_PRESSURE_0P) - ntype = keygroup::KT_PRESSURE_0P; - if(amode == AMODE_PRESSURE_M0) - ntype = keygroup::KT_PRESSURE_M0; - if(amode == AMODE_PRESSURE_MP) - ntype = keygroup::KT_PRESSURE_MP; - if(amode == AMODE_PRESSURE_PM) - ntype = keygroup::KT_PRESSURE_PM; - if(amode == AMODE_PRESSURE_P0) - ntype = keygroup::KT_PRESSURE_P0; - try { - low = boost::lexical_cast(tostdstring(a_low->GetValue())); - mid = boost::lexical_cast(tostdstring(a_mid->GetValue())); - high = boost::lexical_cast(tostdstring(a_high->GetValue())); - tolerance = boost::lexical_cast(tostdstring(a_tolerance->GetValue())); - } catch(...) { - return; - } - if(low < -32768 || low > 32767 || low > mid) - return; - if(mid < -32768 || mid > 32767 || mid > high) - return; - if(high < -32768 || high > 32767) - return; - if(tolerance <= 0 || tolerance >= 1) - return; - runemufn([k, ctype, ntype, low, mid, high, tolerance]() { - if(ctype != ntype) - k->change_type(ntype); - k->change_calibration(low, mid, high, tolerance); - }); -} - -wxeditor_axes::wxeditor_axes(wxWindow* parent) - : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit axes"), wxDefaultPosition, wxSize(-1, -1)) -{ - std::set axisnames; - runemufn([&axisnames]() { axisnames = keygroup::get_axis_set(); }); - - Centre(); - wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); - SetSizer(top_s); - - wxFlexGridSizer* t_s = new wxFlexGridSizer(axisnames.size() + 1, 6, 0, 0); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Name")), 0, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Type")), 0, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Low")), 0, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Mid")), 0, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("High")), 0, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Tolerance")), 0, wxGROW); - for(auto i : axisnames) - axes.push_back(new wxeditor_axes_axis(t_s, this, i)); - top_s->Add(t_s); - - wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); - pbutton_s->AddStretchSpacer(); - pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW); - pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW); - okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(wxeditor_axes::on_ok), NULL, this); - cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(wxeditor_axes::on_cancel), NULL, this); - top_s->Add(pbutton_s, 0, wxGROW); - - t_s->SetSizeHints(this); - top_s->SetSizeHints(this); - Fit(); -} - -wxeditor_axes::~wxeditor_axes() -{ - for(auto i : axes) - delete i; -} - -bool wxeditor_axes::has_axes() -{ - return (axes.size() != 0); -} - -bool wxeditor_axes::ShouldPreventAppExit() const -{ - return false; -} - -void wxeditor_axes::on_value_change(wxCommandEvent& e) -{ - bool all_ok = true; - for(auto i : axes) - all_ok = all_ok && i->is_ok(); - okbutton->Enable(all_ok); -} - -void wxeditor_axes::on_cancel(wxCommandEvent& e) -{ - EndModal(wxID_CANCEL); -} - -void wxeditor_axes::on_ok(wxCommandEvent& e) -{ - for(auto i : axes) - i->apply(); - EndModal(wxID_OK); -} - -void wxeditor_axes_display(wxWindow* parent) -{ - modal_pause_holder hld; - wxDialog* editor; - try { - editor = new wxeditor_axes(parent); - if(dynamic_cast(editor)->has_axes()) - editor->ShowModal(); - else { - wxMessageBox(_T("You don't have joysticks to configure!"), _T("Warning"), wxICON_WARNING | - wxOK); - } - } catch(...) { - } - editor->Destroy(); -} diff --git a/src/platform/wxwidgets/editor-paths.cpp b/src/platform/wxwidgets/editor-paths.cpp deleted file mode 100644 index 9ecf88a8..00000000 --- a/src/platform/wxwidgets/editor-paths.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "platform/wxwidgets/platform.hpp" -#include "core/settings.hpp" -#include "library/string.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include - -#define FIRMWAREPATH "firmwarepath" -#define ROMPATH "rompath" -#define MOVIEPATH "moviepath" - -class wxeditor_paths : public wxDialog -{ -public: - wxeditor_paths(wxWindow* parent); - ~wxeditor_paths(); - bool ShouldPreventAppExit() const; - void on_cancel(wxCommandEvent& e); - void on_ok(wxCommandEvent& e); -private: - wxButton* okbutton; - wxButton* cancel; - wxTextCtrl* rompath; - wxTextCtrl* moviepath; - wxTextCtrl* firmwarepath; -}; - -wxeditor_paths::wxeditor_paths(wxWindow* parent) - : wxDialog(parent, wxID_ANY, wxT("lsnes: Paths"), wxDefaultPosition, wxSize(-1, -1)) -{ - std::string cur_rompath, cur_moviepath, cur_firmwarepath; - runemufn([&cur_firmwarepath, &cur_moviepath, &cur_rompath]() { - cur_firmwarepath = setting::get(FIRMWAREPATH); - cur_rompath = setting::get(ROMPATH); - cur_moviepath = setting::get(MOVIEPATH); - }); - - Centre(); - wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0); - SetSizer(top_s); - - wxFlexGridSizer* t_s = new wxFlexGridSizer(3, 2, 0, 0); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("ROMs:")), 0, wxGROW); - t_s->Add(rompath = new wxTextCtrl(this, wxID_ANY, towxstring(cur_rompath), wxDefaultPosition, wxSize(400, -1)), - 1, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Movies:")), 0, wxGROW); - t_s->Add(moviepath = new wxTextCtrl(this, wxID_ANY, towxstring(cur_moviepath), wxDefaultPosition, - wxSize(400, -1)), 1, wxGROW); - t_s->Add(new wxStaticText(this, wxID_ANY, wxT("Firmware:")), 0, wxGROW); - t_s->Add(firmwarepath = new wxTextCtrl(this, wxID_ANY, towxstring(cur_firmwarepath), wxDefaultPosition, - wxSize(400, -1)), 1, wxGROW); - top_s->Add(t_s); - - wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); - pbutton_s->AddStretchSpacer(); - pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW); - pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW); - okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_paths::on_ok), NULL, this); - cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, - wxCommandEventHandler(wxeditor_paths::on_cancel), NULL, this); - top_s->Add(pbutton_s, 0, wxGROW); - - t_s->SetSizeHints(this); - top_s->SetSizeHints(this); - Fit(); -} - -wxeditor_paths::~wxeditor_paths() -{ -} - -bool wxeditor_paths::ShouldPreventAppExit() const -{ - return false; -} - -void wxeditor_paths::on_cancel(wxCommandEvent& e) -{ - EndModal(wxID_CANCEL); -} - -void wxeditor_paths::on_ok(wxCommandEvent& e) -{ - std::string cur_rompath = tostdstring(rompath->GetValue()); - std::string cur_moviepath = tostdstring(moviepath->GetValue()); - std::string cur_firmwarepath = tostdstring(firmwarepath->GetValue()); - runemufn([cur_firmwarepath, cur_moviepath, cur_rompath]() { - setting::set(ROMPATH, cur_rompath); - setting::set(MOVIEPATH, cur_moviepath); - setting::set(FIRMWAREPATH, cur_firmwarepath); - }); - EndModal(wxID_OK); -} - -void wxeditor_paths_display(wxWindow* parent) -{ - modal_pause_holder hld; - wxDialog* editor; - try { - editor = new wxeditor_paths(parent); - editor->ShowModal(); - } catch(...) { - } - editor->Destroy(); -} diff --git a/src/platform/wxwidgets/editor-screen.cpp b/src/platform/wxwidgets/editor-screen.cpp index 0ea1e11c..3b6f148c 100644 --- a/src/platform/wxwidgets/editor-screen.cpp +++ b/src/platform/wxwidgets/editor-screen.cpp @@ -25,16 +25,13 @@ const char* algo_choices[] = {"Fast Bilinear", "Bilinear", "Bicubic", "Experimen class wxeditor_screen : public wxDialog { public: - wxeditor_screen(wxWindow* parent, double& _horiz, double& _vert, int& _flags); + wxeditor_screen(wxWindow* parent); ~wxeditor_screen(); bool ShouldPreventAppExit() const; void on_value_change(wxCommandEvent& e); void on_cancel(wxCommandEvent& e); void on_ok(wxCommandEvent& e); private: - double& horiz; - double& vert; - int& flags; wxButton* okbutton; wxButton* cancel; wxTextCtrl* horizbox; @@ -42,9 +39,8 @@ private: wxComboBox* algo; }; -wxeditor_screen::wxeditor_screen(wxWindow* parent, double& _horiz, double& _vert, int& _flags) - : wxDialog(parent, wxID_ANY, wxT("lsnes: Screen scaling"), wxDefaultPosition, wxSize(-1, -1)), - horiz(_horiz), vert(_vert), flags(_flags) +wxeditor_screen::wxeditor_screen(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxT("lsnes: Screen scaling"), wxDefaultPosition, wxSize(-1, -1)) { std::set axisnames; std::string h_x, v_x; @@ -54,11 +50,11 @@ wxeditor_screen::wxeditor_screen(wxWindow* parent, double& _horiz, double& _vert for(size_t i = 0; i < sizeof(_algo_choices) / sizeof(_algo_choices[0]); i++) _algo_choices[i] = towxstring(algo_choices[i]); - h_x = (stringfmt() << _horiz).str(); - v_x = (stringfmt() << _vert).str(); + h_x = (stringfmt() << horizontal_scale_factor).str(); + v_x = (stringfmt() << vertical_scale_factor).str(); algoidx = 0; for(size_t i = 0; i < sizeof(algo_choices) / sizeof(_algo_choices[0]); i++) - if(_flags & (1 << i)) { + if(scaling_flags & (1 << i)) { algoidx = i; break; } @@ -164,20 +160,18 @@ void wxeditor_screen::on_ok(wxCommandEvent& e) return; } - horiz = hscale; - vert = vscale; - flags = newflags; + horizontal_scale_factor = hscale; + vertical_scale_factor = vscale; + scaling_flags = newflags; EndModal(wxID_OK); } - - -void wxeditor_screen_display(wxWindow* parent, double& horiz, double& vert, int& flags) +void wxeditor_screen_display(wxWindow* parent) { modal_pause_holder hld; wxDialog* editor; try { - editor = new wxeditor_screen(parent, horiz, vert, flags); + editor = new wxeditor_screen(parent); editor->ShowModal(); } catch(...) { } diff --git a/src/platform/wxwidgets/mainwindow.cpp b/src/platform/wxwidgets/mainwindow.cpp index d8c4608a..d66cd3f5 100644 --- a/src/platform/wxwidgets/mainwindow.cpp +++ b/src/platform/wxwidgets/mainwindow.cpp @@ -64,7 +64,6 @@ enum wxID_EDIT_AUTHORS, wxID_AUTOHOLD_FIRST, wxID_AUTOHOLD_LAST = wxID_AUTOHOLD_FIRST + 1023, - wxID_EDIT_AXES, wxID_EDIT_SETTINGS, wxID_EDIT_KEYBINDINGS, wxID_EDIT_ALIAS, @@ -82,7 +81,6 @@ enum wxID_SET_SPEED, wxID_SET_VOLUME, wxID_SET_SCREEN, - wxID_SET_PATHS, wxID_SPEED_5, wxID_SPEED_10, wxID_SPEED_17, @@ -95,15 +93,17 @@ enum wxID_SPEED_200, wxID_SPEED_300, wxID_SPEED_TURBO, - wxID_LOAD_LIBRARY + wxID_LOAD_LIBRARY, + wxID_SETTINGS, }; +double horizontal_scale_factor = 1.0; +double vertical_scale_factor = 1.0; +int scaling_flags = SWS_POINT; + namespace { - double horizontal_multiplier = 1.0; - double vertical_multiplier = 1.0; - int libswscale_flags = SWS_POINT; std::string last_volume = "0dB"; unsigned char* screen_buffer; uint32_t old_width; @@ -615,18 +615,18 @@ void wxwin_mainwindow::panel::on_paint(wxPaintEvent& e) uint8_t* dstp[1]; int dsts[1]; wxPaintDC dc(this); - uint32_t tw = main_screen.width * horizontal_multiplier + 0.5; - uint32_t th = main_screen.height * vertical_multiplier + 0.5; - if(!screen_buffer || tw != old_width || th != old_height || libswscale_flags != old_flags) { + uint32_t tw = main_screen.width * horizontal_scale_factor + 0.5; + uint32_t th = main_screen.height * vertical_scale_factor + 0.5; + if(!screen_buffer || tw != old_width || th != old_height || scaling_flags != old_flags) { if(screen_buffer) delete[] screen_buffer; old_height = th; old_width = tw; - old_flags = libswscale_flags; + old_flags = scaling_flags; uint32_t w = main_screen.width; uint32_t h = main_screen.height; if(w && h) - ctx = sws_getCachedContext(ctx, w, h, PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, libswscale_flags, + ctx = sws_getCachedContext(ctx, w, h, PIX_FMT_RGBA, tw, th, PIX_FMT_BGR24, scaling_flags, NULL, NULL, NULL); tw = max(tw, static_cast(128)); th = max(th, static_cast(112)); @@ -685,31 +685,12 @@ wxwin_mainwindow::wxwin_mainwindow() SetMenuBar(menubar); menu_start(wxT("lsnes")); - menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode")); - menu_check(wxID_READONLY_MODE, is_readonly_mode()); - menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors...")); menu_entry_check(wxID_SHOW_STATUS, wxT("Show/Hide status panel")); menu_check(wxID_SHOW_STATUS, true); - menu_start_sub(wxT("Speed")); - menu_entry(wxID_SPEED_5, wxT("1/20x")); - menu_entry(wxID_SPEED_10, wxT("1/10x")); - menu_entry(wxID_SPEED_17, wxT("1/6x")); - menu_entry(wxID_SPEED_20, wxT("1/5x")); - menu_entry(wxID_SPEED_25, wxT("1/4x")); - menu_entry(wxID_SPEED_33, wxT("1/3x")); - menu_entry(wxID_SPEED_50, wxT("1/2x")); - menu_entry(wxID_SPEED_100, wxT("1x")); - menu_entry(wxID_SPEED_150, wxT("1.5x")); - menu_entry(wxID_SPEED_200, wxT("2x")); - menu_entry(wxID_SPEED_300, wxT("3x")); - menu_entry(wxID_SPEED_TURBO, wxT("Turbo")); - menu_entry(wxID_SET_SPEED, wxT("Set...")); - menu_end_sub(); if(load_library_supported) { menu_entry(wxID_LOAD_LIBRARY, towxstring(std::string("Load ") + library_is_called)); } - menu_special_sub(wxT("Dump video"), reinterpret_cast(dmenu = new dumper_menu(this, - wxID_DUMP_FIRST, wxID_DUMP_LAST))); + menu_entry(wxID_SETTINGS, wxT("Configure emulator...")); if(platform::sound_initialized()) { menu_separator(); menu_entry_check(wxID_AUDIO_ENABLED, wxT("Sounds enabled")); @@ -749,7 +730,22 @@ wxwin_mainwindow::wxwin_mainwindow() //Autohold menu: (ACOS) menu_special(wxT("Autohold"), reinterpret_cast(ahmenu = new autohold_menu(this))); blistener->set_autohold_menu(reinterpret_cast(ahmenu)); - //Scripting menu: (ACOS)ERU + + menu_start(wxT("Speed")); + menu_entry(wxID_SPEED_5, wxT("1/20x")); + menu_entry(wxID_SPEED_10, wxT("1/10x")); + menu_entry(wxID_SPEED_17, wxT("1/6x")); + menu_entry(wxID_SPEED_20, wxT("1/5x")); + menu_entry(wxID_SPEED_25, wxT("1/4x")); + menu_entry(wxID_SPEED_33, wxT("1/3x")); + menu_entry(wxID_SPEED_50, wxT("1/2x")); + menu_entry(wxID_SPEED_100, wxT("1x")); + menu_entry(wxID_SPEED_150, wxT("1.5x")); + menu_entry(wxID_SPEED_200, wxT("2x")); + menu_entry(wxID_SPEED_300, wxT("3x")); + menu_entry(wxID_SPEED_TURBO, wxT("Turbo")); + menu_entry(wxID_SET_SPEED, wxT("Set...")); + menu_start(wxT("Scripting")); menu_entry(wxID_RUN_SCRIPT, wxT("Run script...")); if(lua_supported) { @@ -764,16 +760,22 @@ wxwin_mainwindow::wxwin_mainwindow() menu_entry(wxID_SAVE_MEMORYWATCH, wxT("Save memory watch...")); menu_separator(); menu_entry(wxID_MEMORY_SEARCH, wxT("Memory Search...")); - //Settings menu: (ACFOS) + + menu_start(wxT("Movie")); + menu_entry_check(wxID_READONLY_MODE, wxT("Readonly mode")); + menu_check(wxID_READONLY_MODE, is_readonly_mode()); + menu_entry(wxID_EDIT_AUTHORS, wxT("Edit game name && authors...")); + + menu_special(wxT("Capture"), reinterpret_cast(dmenu = new dumper_menu(this, + wxID_DUMP_FIRST, wxID_DUMP_LAST))); + menu_start(wxT("Settings")); - menu_entry(wxID_EDIT_AXES, wxT("Configure axes...")); menu_entry(wxID_EDIT_SETTINGS, wxT("Configure settings...")); menu_entry(wxID_EDIT_KEYBINDINGS, wxT("Configure keybindings...")); menu_entry(wxID_EDIT_ALIAS, wxT("Configure aliases...")); menu_entry(wxID_EDIT_JUKEBOX, wxT("Configure jukebox...")); menu_separator(); menu_entry(wxID_SET_SCREEN, wxT("Set screen scaling...")); - menu_entry(wxID_SET_PATHS, wxT("Set paths...")); menu_entry(wxID_EDIT_HOTKEYS, wxT("Configure hotkeys...")); } @@ -914,9 +916,6 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) update_movie_state(); }); return; - case wxID_EDIT_AXES: - wxeditor_axes_display(this); - return; case wxID_EDIT_AUTHORS: wxeditor_authors_display(this); return; @@ -1103,6 +1102,7 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) runemufn([&bad, &value]() { try { setting::set("targetfps", value); } catch(...) { bad = true; } }); if(bad) wxMessageBox(wxT("Invalid speed"), _T("Error"), wxICON_EXCLAMATION | wxOK, this); + return } case wxID_SET_VOLUME: { std::string value; @@ -1122,12 +1122,10 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) } last_volume = value; runemufn([parsed]() { platform::global_volume = parsed; }); + return; } case wxID_SET_SCREEN: - wxeditor_screen_display(this, horizontal_multiplier, vertical_multiplier, libswscale_flags); - return; - case wxID_SET_PATHS: - wxeditor_paths_display(this); + wxeditor_screen_display(this); return; case wxID_SPEED_5: set_speed(5); @@ -1168,6 +1166,10 @@ void wxwin_mainwindow::handle_menu_click_cancelable(wxCommandEvent& e) case wxID_LOAD_LIBRARY: { std::string name = std::string("load ") + library_is_called; load_library(pick_file(this, name, ".")); + break; } + case wxID_SETTINGS: + wxsetingsdialog_display(this); + break; }; } diff --git a/src/platform/wxwidgets/settings.cpp b/src/platform/wxwidgets/settings.cpp new file mode 100644 index 00000000..1f8f7113 --- /dev/null +++ b/src/platform/wxwidgets/settings.cpp @@ -0,0 +1,452 @@ +#include "platform/wxwidgets/platform.hpp" +#include "core/settings.hpp" +#include "library/string.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern "C" +{ +#ifndef UINT64_C +#define UINT64_C(val) val##ULL +#endif +#include +} + +#define AMODE_DISABLED "Disabled" +#define AMODE_AXIS_PAIR "Axis" +#define AMODE_AXIS_PAIR_INVERSE "Axis (inverted)" +#define AMODE_PRESSURE_M0 "Pressure - to 0" +#define AMODE_PRESSURE_MP "Pressure - to +" +#define AMODE_PRESSURE_0M "Pressure 0 to -" +#define AMODE_PRESSURE_0P "Pressure 0 to +" +#define AMODE_PRESSURE_PM "Pressure + to -" +#define AMODE_PRESSURE_P0 "Pressure + to 0" +#define FIRMWAREPATH "firmwarepath" +#define ROMPATH "rompath" +#define MOVIEPATH "moviepath" + + + +const char* scalealgo_choices[] = {"Fast Bilinear", "Bilinear", "Bicubic", "Experimential", "Point", "Area", + "Bicubic-Linear", "Gauss", "Sinc", "Lanczos", "Spline"}; + +class wxeditor_esettings_joystick_aconfig : public wxDialog +{ +public: + wxeditor_esettings_joystick_aconfig(wxWindow* parent, const std::string& _aname); + ~wxeditor_esettings_joystick_aconfig(); + void on_ok(wxCommandEvent& e); + void on_cancel(wxCommandEvent& e); +private: + std::string aname; + wxComboBox* type; + wxTextCtrl* low; + wxTextCtrl* mid; + wxTextCtrl* hi; + wxTextCtrl* tol; + wxButton* okbutton; + wxButton* cancel; +}; + +wxeditor_esettings_joystick_aconfig::wxeditor_esettings_joystick_aconfig(wxWindow* parent, const std::string& _aname) + : wxDialog(parent, -1, towxstring("Configure axis " + _aname)) +{ + wxString choices[9]; + int didx = 1; + choices[0] = wxT(AMODE_DISABLED); + choices[1] = wxT(AMODE_AXIS_PAIR); + choices[2] = wxT(AMODE_AXIS_PAIR_INVERSE); + choices[3] = wxT(AMODE_PRESSURE_M0); + choices[4] = wxT(AMODE_PRESSURE_MP); + choices[5] = wxT(AMODE_PRESSURE_0M); + choices[6] = wxT(AMODE_PRESSURE_0P); + choices[7] = wxT(AMODE_PRESSURE_PM); + choices[8] = wxT(AMODE_PRESSURE_P0); + + aname = _aname; + keygroup::parameters params; + + runemufn([aname, ¶ms]() { + auto k = keygroup::lookup_by_name(aname); + if(k) + params = k->get_parameters(); + }); + + switch(params.ktype) { + case keygroup::KT_DISABLED: didx = 0; break; + case keygroup::KT_AXIS_PAIR: didx = 1; break; + case keygroup::KT_AXIS_PAIR_INVERSE: didx = 2; break; + case keygroup::KT_PRESSURE_M0: didx = 3; break; + case keygroup::KT_PRESSURE_MP: didx = 4; break; + case keygroup::KT_PRESSURE_0M: didx = 5; break; + case keygroup::KT_PRESSURE_0P: didx = 6; break; + case keygroup::KT_PRESSURE_PM: didx = 7; break; + case keygroup::KT_PRESSURE_P0: didx = 8; break; + }; + + Centre(); + wxSizer* top_s = new wxBoxSizer(wxVERTICAL); + SetSizer(top_s); + + wxFlexGridSizer* t_s = new wxFlexGridSizer(5, 2, 0, 0); + t_s->Add(new wxStaticText(this, -1, wxT("Type: ")), 0, wxGROW); + t_s->Add(type = new wxComboBox(this, wxID_ANY, choices[didx], wxDefaultPosition, wxDefaultSize, + 9, choices, wxCB_READONLY), 1, wxGROW); + t_s->Add(new wxStaticText(this, -1, wxT("Low: ")), 0, wxGROW); + t_s->Add(low = new wxTextCtrl(this, -1, towxstring((stringfmt() << params.cal_left).str()), wxDefaultPosition, + wxSize(100, -1)), 1, wxGROW); + t_s->Add(new wxStaticText(this, -1, wxT("Middle: ")), 0, wxGROW); + t_s->Add(mid = new wxTextCtrl(this, -1, towxstring((stringfmt() << params.cal_center).str()), + wxDefaultPosition, wxSize(100, -1)), 1, wxGROW); + t_s->Add(new wxStaticText(this, -1, wxT("High: ")), 0, wxGROW); + t_s->Add(hi = new wxTextCtrl(this, -1, towxstring((stringfmt() << params.cal_right).str()), + wxDefaultPosition, wxSize(100, -1)), 1, wxGROW); + t_s->Add(new wxStaticText(this, -1, wxT("Tolerance: ")), 0, wxGROW); + t_s->Add(tol = new wxTextCtrl(this, -1, towxstring((stringfmt() << params.cal_tolerance).str()), + wxDefaultPosition, wxSize(100, -1)), 1, wxGROW); + top_s->Add(t_s); + + wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + pbutton_s->AddStretchSpacer(); + pbutton_s->Add(okbutton = new wxButton(this, wxID_OK, wxT("OK")), 0, wxGROW); + pbutton_s->Add(cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel")), 0, wxGROW); + okbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxeditor_esettings_joystick_aconfig::on_ok), NULL, this); + cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxeditor_esettings_joystick_aconfig::on_cancel), NULL, this); + top_s->Add(pbutton_s, 0, wxGROW); + + t_s->SetSizeHints(this); + top_s->SetSizeHints(this); + Fit(); +} + +wxeditor_esettings_joystick_aconfig::~wxeditor_esettings_joystick_aconfig() +{ +} + +void wxeditor_esettings_joystick_aconfig::on_ok(wxCommandEvent& e) +{ + std::string _type = tostdstring(type->GetValue()); + std::string _low = tostdstring(low->GetValue()); + std::string _mid = tostdstring(mid->GetValue()); + std::string _hi = tostdstring(hi->GetValue()); + std::string _tol = tostdstring(tol->GetValue()); + enum keygroup::type _ctype = keygroup::KT_DISABLED; + enum keygroup::type _ntype = keygroup::KT_AXIS_PAIR; + int32_t nlow, nmid, nhi; + double ntol; + keygroup* k; + + runemufn([&k, aname, &_ctype]() { + k = keygroup::lookup_by_name(aname); + if(k) + _ctype = k->get_parameters().ktype; + }); + if(!k) { + //Axis gone away? + EndModal(wxID_OK); + return; + } + + const char* bad_what = NULL; + try { + bad_what = "Bad axis type"; + if(_type == AMODE_AXIS_PAIR) _ntype = keygroup::KT_AXIS_PAIR; + else if(_type == AMODE_AXIS_PAIR_INVERSE) _ntype = keygroup::KT_AXIS_PAIR_INVERSE; + else if(_type == AMODE_DISABLED) _ntype = keygroup::KT_DISABLED; + else if(_type == AMODE_PRESSURE_0M) _ntype = keygroup::KT_PRESSURE_0M; + else if(_type == AMODE_PRESSURE_0P) _ntype = keygroup::KT_PRESSURE_0P; + else if(_type == AMODE_PRESSURE_M0) _ntype = keygroup::KT_PRESSURE_M0; + else if(_type == AMODE_PRESSURE_MP) _ntype = keygroup::KT_PRESSURE_MP; + else if(_type == AMODE_PRESSURE_P0) _ntype = keygroup::KT_PRESSURE_P0; + else if(_type == AMODE_PRESSURE_PM) _ntype = keygroup::KT_PRESSURE_PM; + else + throw 42; + bad_what = "Bad low calibration value (range is -32768 - 32767)"; + nlow = boost::lexical_cast(_low); + if(nlow < -32768 || nlow > 32767) + throw 42; + bad_what = "Bad middle calibration value (range is -32768 - 32767)"; + nmid = boost::lexical_cast(_mid); + if(nmid < -32768 || nmid > 32767) + throw 42; + bad_what = "Bad high calibration value (range is -32768 - 32767)"; + nhi = boost::lexical_cast(_hi); + if(nhi < -32768 || nhi > 32767) + throw 42; + bad_what = "Bad tolerance (range is 0 - 1)"; + ntol = boost::lexical_cast(_tol); + if(ntol <= 0 || ntol >= 1) + throw 42; + } catch(...) { + wxMessageBox(towxstring(bad_what), _T("Error"), wxICON_EXCLAMATION | wxOK); + return; + } + + runemufn([&k, _ctype, _ntype, nlow, nmid, nhi, ntol]() { + if(_ctype != _ntype) + k->change_type(_ntype); + k->change_calibration(nlow, nmid, nhi, ntol); + }); + EndModal(wxID_OK); +} + +void wxeditor_esettings_joystick_aconfig::on_cancel(wxCommandEvent& e) +{ + EndModal(wxID_CANCEL); +} + +class wxeditor_esettings_joystick : public wxPanel +{ +public: + wxeditor_esettings_joystick(wxWindow* parent); + ~wxeditor_esettings_joystick(); + void on_configure(wxCommandEvent& e); +private: + void refresh(); + wxSizer* jgrid; + std::map buttons; + std::map ids; + int last_id; +}; + +namespace +{ + std::string formattype(keygroup::type t) + { + if(t == keygroup::KT_AXIS_PAIR) return AMODE_AXIS_PAIR; + else if(t == keygroup::KT_AXIS_PAIR_INVERSE) return AMODE_AXIS_PAIR_INVERSE; + else if(t == keygroup::KT_PRESSURE_0M) return AMODE_PRESSURE_0M; + else if(t == keygroup::KT_PRESSURE_0P) return AMODE_PRESSURE_0P; + else if(t == keygroup::KT_PRESSURE_M0) return AMODE_PRESSURE_M0; + else if(t == keygroup::KT_PRESSURE_MP) return AMODE_PRESSURE_MP; + else if(t == keygroup::KT_PRESSURE_P0) return AMODE_PRESSURE_P0; + else if(t == keygroup::KT_PRESSURE_PM) return AMODE_PRESSURE_PM; + else return "Unknown"; + } + + std::string formatsettings(const std::string& name, const keygroup::parameters& s) + { + return (stringfmt() << name << ": " << formattype(s.ktype) << " low:" << s.cal_left << " mid:" + << s.cal_center << " high:" << s.cal_right << " tolerance:" << s.cal_tolerance).str(); + } +} + +wxeditor_esettings_joystick::wxeditor_esettings_joystick(wxWindow* parent) + : wxPanel(parent, -1) +{ + last_id = wxID_HIGHEST + 1; + SetSizer(jgrid = new wxBoxSizer(wxVERTICAL)); + refresh(); + jgrid->SetSizeHints(this); + Fit(); +} + +wxeditor_esettings_joystick::~wxeditor_esettings_joystick() +{ +} + +void wxeditor_esettings_joystick::on_configure(wxCommandEvent& e) +{ + if(!ids.count(e.GetId())) + return; + wxDialog* d = new wxeditor_esettings_joystick_aconfig(this, ids[e.GetId()]); + d->ShowModal(); + d->Destroy(); + refresh(); +} + +void wxeditor_esettings_joystick::refresh() +{ + //Collect the new settings. + std::map x; + runemufn([&x]() { + auto axisnames = keygroup::get_axis_set(); + for(auto i : axisnames) { + keygroup* k = keygroup::lookup_by_name(i); + if(k) + x[i] = k->get_parameters(); + } + }); + + for(auto i : x) { + if(buttons.count(i.first)) { + //Okay, this already exists. Update. + buttons[i.first]->SetLabel(towxstring(formatsettings(i.first, i.second))); + if(!buttons[i.first]->IsShown()) { + jgrid->Add(buttons[i.first], 1, wxGROW); + buttons[i.first]->Show(); + } + } else { + //New button. + ids[last_id] = i.first; + buttons[i.first] = new wxButton(this, last_id++, towxstring(formatsettings(i.first, + i.second))); + buttons[i.first]->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxeditor_esettings_joystick::on_configure), NULL, this); + jgrid->Add(buttons[i.first], 1, wxGROW); + } + } + for(auto i : buttons) { + if(!x.count(i.first)) { + //Removed button. + i.second->Hide(); + jgrid->Detach(i.second); + } + } + jgrid->Layout(); + Fit(); +} + +class wxeditor_esettings_paths : public wxPanel +{ +public: + wxeditor_esettings_paths(wxWindow* parent); + ~wxeditor_esettings_paths(); + void on_configure(wxCommandEvent& e); +private: + void refresh(); + wxStaticText* rompath; + wxStaticText* firmpath; + wxStaticText* savepath; + wxFlexGridSizer* top_s; +}; + +wxeditor_esettings_paths::wxeditor_esettings_paths(wxWindow* parent) + : wxPanel(parent, -1) +{ + wxButton* tmp; + top_s = new wxFlexGridSizer(3, 3, 0, 0); + SetSizer(top_s); + top_s->Add(new wxStaticText(this, -1, wxT("ROM path")), 0, wxGROW); + top_s->Add(rompath = new wxStaticText(this, -1, wxT("")), 1, wxGROW); + top_s->Add(tmp = new wxButton(this, wxID_HIGHEST + 1, wxT("Change...")), 0, wxGROW); + tmp->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_esettings_paths::on_configure), NULL, + this); + top_s->Add(new wxStaticText(this, -1, wxT("Firmware path")), 0, wxGROW); + top_s->Add(firmpath = new wxStaticText(this, -1, wxT("")), 1, wxGROW); + top_s->Add(tmp = new wxButton(this, wxID_HIGHEST + 2, wxT("Change...")), 0, wxGROW); + tmp->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_esettings_paths::on_configure), NULL, + this); + top_s->Add(new wxStaticText(this, -1, wxT("Save path")), 0, wxGROW); + top_s->Add(savepath = new wxStaticText(this, -1, wxT("")), 1, wxGROW); + top_s->Add(tmp = new wxButton(this, wxID_HIGHEST + 3, wxT("Change...")), 0, wxGROW); + tmp->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxeditor_esettings_paths::on_configure), NULL, + this); + refresh(); + top_s->SetSizeHints(this); + Fit(); +} +wxeditor_esettings_paths::~wxeditor_esettings_paths() +{ +} + +void wxeditor_esettings_paths::on_configure(wxCommandEvent& e) +{ + std::string name; + if(e.GetId() == wxID_HIGHEST + 1) + name = ROMPATH; + else if(e.GetId() == wxID_HIGHEST + 2) + name = FIRMWAREPATH; + else if(e.GetId() == wxID_HIGHEST + 3) + name = MOVIEPATH; + else + return; + std::string val; + runemufn([&val, name]() { val = setting::get(name); }); + try { + val = pick_text(this, "Change path to", "Enter new path:", val); + } catch(...) { + refresh(); + return; + } + runemufn([val, name]() { setting::set(name, val); }); + refresh(); +} + +void wxeditor_esettings_paths::refresh() +{ + std::string rpath, fpath, spath; + runemufn([&rpath, &fpath, &spath]() { + fpath = setting::get(FIRMWAREPATH); + rpath = setting::get(ROMPATH); + spath = setting::get(MOVIEPATH); + }); + rompath->SetLabel(towxstring(rpath)); + firmpath->SetLabel(towxstring(fpath)); + savepath->SetLabel(towxstring(spath)); + top_s->Layout(); + Fit(); +} + +class wxeditor_esettings : public wxDialog +{ +public: + wxeditor_esettings(wxWindow* parent); + ~wxeditor_esettings(); + bool ShouldPreventAppExit() const; + void on_close(wxCommandEvent& e); +private: + wxWindow* joystick_window; + wxNotebook* tabset; + wxButton* closebutton; +}; + +wxeditor_esettings::wxeditor_esettings(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxT("lsnes: Configure emulator"), wxDefaultPosition, wxSize(-1, -1)) +{ + Centre(); + wxSizer* top_s = new wxBoxSizer(wxVERTICAL); + SetSizer(top_s); + + tabset = new wxNotebook(this, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP); + tabset->AddPage(new wxeditor_esettings_joystick(tabset), wxT("Joysticks")); + tabset->AddPage(new wxeditor_esettings_paths(tabset), wxT("Paths")); + top_s->Add(tabset, 1, wxGROW); + + wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL); + pbutton_s->AddStretchSpacer(); + pbutton_s->Add(closebutton = new wxButton(this, wxID_ANY, wxT("Close")), 0, wxGROW); + closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED, + wxCommandEventHandler(wxeditor_esettings::on_close), NULL, this); + top_s->Add(pbutton_s, 0, wxGROW); + + top_s->SetSizeHints(this); + Fit(); +} + +wxeditor_esettings::~wxeditor_esettings() +{ +} + +bool wxeditor_esettings::ShouldPreventAppExit() const +{ + return false; +} + +void wxeditor_esettings::on_close(wxCommandEvent& e) +{ + EndModal(wxID_OK); +} + +void wxsetingsdialog_display(wxWindow* parent) +{ + modal_pause_holder hld; + wxDialog* editor; + try { + editor = new wxeditor_esettings(parent); + editor->ShowModal(); + } catch(...) { + } + editor->Destroy(); +} diff --git a/src/util/lsnes-dumpavi.cpp b/src/util/lsnes-dumpavi.cpp index e1b9326d..03f3e776 100644 --- a/src/util/lsnes-dumpavi.cpp +++ b/src/util/lsnes-dumpavi.cpp @@ -102,6 +102,20 @@ namespace return *_dumper; } + std::string format_details(unsigned detail) + { + std::string r; + if((detail & adv_dumper::target_type_mask) == adv_dumper::target_type_file) + r = r + "TARGET_FILE"; + else if((detail & adv_dumper::target_type_mask) == adv_dumper::target_type_prefix) + r = r + "TARGET_PREFIX"; + else if((detail & adv_dumper::target_type_mask) == adv_dumper::target_type_special) + r = r + "TARGET_SPECIAL"; + else + r = r + "TARGET_UNKNOWN"; + return r; + } + adv_dumper& get_dumper(const std::vector& cmdline, std::string& mode, std::string& prefix, uint64_t& length) { @@ -171,18 +185,17 @@ namespace adv_dumper& _dumper = locate_dumper(dumper); std::set modes = _dumper.list_submodes(); if(modes.empty()) { - if(_dumper.wants_prefix("")) - std::cout << "No modes available for " << dumper << " (multi)" << std::endl; - else - std::cout << "No modes available for " << dumper << " (single)" << std::endl; + unsigned d = _dumper.mode_details(""); + std::cout << "No modes available for " << dumper << " (" << format_details(d) << ")" + << std::endl; exit(0); } std::cout << "Modes available for " << dumper << ":" << std::endl; - for(auto i : modes) - if(_dumper.wants_prefix(i)) - std::cout << i << "\tmulti\t" << _dumper.modename(i) << std::endl; - else - std::cout << i << "\tsingle\t" << _dumper.modename(i) << std::endl; + for(auto i : modes) { + unsigned d = _dumper.mode_details(i); + std::cout << i << "\t" << _dumper.modename(i) << "\t(" << format_details(d) << ")" + << std::endl; + } exit(0); } adv_dumper& _dumper = locate_dumper(dumper); diff --git a/src/video/avi.cpp b/src/video/avi.cpp index 6d2d267d..b8dbffef 100644 --- a/src/video/avi.cpp +++ b/src/video/avi.cpp @@ -280,9 +280,9 @@ namespace return x; } - bool wants_prefix(const std::string& mode) throw() + unsigned mode_details(const std::string& mode) throw() { - return true; + return target_type_prefix; } std::string name() throw(std::bad_alloc) diff --git a/src/video/jmd.cpp b/src/video/jmd.cpp index 8b945f3d..d840f124 100644 --- a/src/video/jmd.cpp +++ b/src/video/jmd.cpp @@ -2,6 +2,7 @@ #include "core/dispatch.hpp" #include "core/settings.hpp" #include "library/serialization.hpp" +#include "video/tcp.hpp" #include #include @@ -18,16 +19,27 @@ namespace { numeric_setting clevel("jmd-compression", 0, 9, 7); + void deleter_fn(void* f) + { + delete reinterpret_cast(f); + } + class jmd_avsnoop : public information_dispatch { public: - jmd_avsnoop(const std::string& filename) throw(std::bad_alloc) + jmd_avsnoop(const std::string& filename, bool tcp_flag) throw(std::bad_alloc) : information_dispatch("dump-jmd") { enable_send_sound(); complevel = clevel; - jmd.open(filename.c_str(), std::ios::out | std::ios::binary); - if(!jmd) + if(tcp_flag) { + jmd = &(socket_address(filename).connect()); + deleter = socket_address::deleter(); + } else { + jmd = new std::ofstream(filename.c_str(), std::ios::out | std::ios::binary); + deleter = deleter_fn; + } + if(!*jmd) throw std::runtime_error("Can't open output JMD file."); last_written_ts = 0; //Write the segment tables. @@ -50,8 +62,8 @@ namespace /* Dummy channel header. */ 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 'd', 'u' }; - jmd.write(header, sizeof(header)); - if(!jmd) + jmd->write(header, sizeof(header)); + if(!*jmd) throw std::runtime_error("Can't write JMD header and segment table"); have_dumped_frame = false; audio_w = 0; @@ -103,18 +115,22 @@ namespace void on_dump_end() { + if(!jmd) + return; flush_buffers(true); if(last_written_ts > maxtc) { - jmd.close(); + deleter(jmd); + jmd = NULL; return; } char dummypacket[8] = {0x00, 0x03}; write32ube(dummypacket + 2, maxtc - last_written_ts); last_written_ts = maxtc; - jmd.write(dummypacket, sizeof(dummypacket)); - if(!jmd) + jmd->write(dummypacket, sizeof(dummypacket)); + if(!*jmd) throw std::runtime_error("Can't write JMD ending dummy packet"); - jmd.close(); + deleter(jmd); + jmd = NULL; } void on_gameinfo(const struct gameinfo_struct& gi) @@ -278,12 +294,12 @@ namespace videopacketh[7 + lneed++] = 0x80 | ((datasize >> shift) & 0x7F); videopacketh[7 + lneed++] = (datasize & 0x7F); - jmd.write(videopacketh, 7 + lneed); - if(!jmd) + jmd->write(videopacketh, 7 + lneed); + if(!*jmd) throw std::runtime_error("Can't write JMD video packet header"); if(datasize > 0) - jmd.write(&f.data[0], datasize); - if(!jmd) + jmd->write(&f.data[0], datasize); + if(!*jmd) throw std::runtime_error("Can't write JMD video packet body"); } @@ -295,12 +311,13 @@ namespace last_written_ts = s.ts; write16sbe(soundpacket + 8, s.l); write16sbe(soundpacket + 10, s.r); - jmd.write(soundpacket, sizeof(soundpacket)); - if(!jmd) + jmd->write(soundpacket, sizeof(soundpacket)); + if(!*jmd) throw std::runtime_error("Can't write JMD sound packet"); } - std::ofstream jmd; + std::ostream* jmd; + void (*deleter)(void* f); uint64_t last_written_ts; unsigned complevel; }; @@ -315,12 +332,14 @@ namespace std::set list_submodes() throw(std::bad_alloc) { std::set x; + x.insert("file"); + x.insert("tcp"); return x; } - bool wants_prefix(const std::string& mode) throw() + unsigned mode_details(const std::string& mode) throw() { - return false; + return (mode == "tcp") ? target_type_special : target_type_file; } std::string name() throw(std::bad_alloc) @@ -330,7 +349,7 @@ namespace std::string modename(const std::string& mode) throw(std::bad_alloc) { - return ""; + return (mode == "tcp") ? "over TCP/IP" : "to file"; } bool busy() @@ -342,11 +361,11 @@ namespace std::runtime_error) { if(prefix == "") - throw std::runtime_error("Expected filename"); + throw std::runtime_error("Expected target"); if(vid_dumper) throw std::runtime_error("JMD dumping already in progress"); try { - vid_dumper = new jmd_avsnoop(prefix); + vid_dumper = new jmd_avsnoop(prefix, mode == "tcp"); } catch(std::bad_alloc& e) { throw; } catch(std::exception& e) { diff --git a/src/video/raw.cpp b/src/video/raw.cpp index ad509391..3c7393a3 100644 --- a/src/video/raw.cpp +++ b/src/video/raw.cpp @@ -1,5 +1,6 @@ #include "core/advdumper.hpp" #include "core/dispatch.hpp" +#include "video/tcp.hpp" #include "library/serialization.hpp" #include @@ -10,29 +11,6 @@ #include #include #include -#ifndef NO_TCP_SOCKETS -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(_WIN32) || defined(_WIN64) -//Why the fuck does windows have nonstandard socket API??? -#define _WIN32_WINNT 0x0501 -#include -#include -struct sockaddr_un { int sun_family; char sun_path[108]; }; -#else -#include -#include -#include -#endif -#include -#endif #define IS_RGB(m) (((m) + ((m) >> 3)) & 2) #define IS_64(m) (m % 5 < 2) @@ -42,194 +20,6 @@ struct sockaddr_un { int sun_family; char sun_path[108]; }; namespace { -#ifndef NO_TCP_SOCKETS - //Increment port number by 1. - void mung_sockaddr(struct sockaddr* addr, socklen_t addrlen) - { - switch(addr->sa_family) { - case AF_INET: { //IPv4 - struct sockaddr_in* _addr = (struct sockaddr_in*)addr; - _addr->sin_port = htons(htons(_addr->sin_port) + 1); - break; - } - case AF_INET6: { //IPv6 - struct sockaddr_in6* _addr = (struct sockaddr_in6*)addr; - _addr->sin6_port = htons(htons(_addr->sin6_port) + 1); - break; - } - case AF_UNIX: { //Unix domain sockets. - struct sockaddr_un* _addr = (struct sockaddr_un*)addr; - const char* b1 = (char*)_addr; - const char* b2 = (char*)&_addr->sun_path; - size_t maxpath = addrlen - (b2 - b1); - for(size_t i = 0; i < maxpath; i++) - if(i && !_addr->sun_path[i]) { - maxpath = i; - break; - } - if(!maxpath) - throw std::runtime_error("Eh, empty unix domain socket path?"); - _addr->sun_path[maxpath - 1]++; - break; - } - default: - throw std::runtime_error("This address family is not supported, sorry."); - } - } - - int compat_connect(int fd, struct sockaddr* addr, socklen_t addrlen) - { -#if defined(_WIN32) || defined(_WIN64) - return connect(fd, addr, addrlen) ? -1 : 0; -#else - return connect(fd, addr, addrlen); -#endif - } - - std::pair establish_connections(struct addrinfo* i) - { - struct sockaddr* addr = i->ai_addr; - socklen_t addrlen = i->ai_addrlen; - int a = socket(i->ai_family, i->ai_socktype, i->ai_protocol); - if(a < 0) { - int err = errno; - throw std::runtime_error(std::string("socket: ") + strerror(err)); - } - int b = socket(i->ai_family, i->ai_socktype, i->ai_protocol); - if(b < 0) { - int err = errno; - close(a); - throw std::runtime_error(std::string("socket: ") + strerror(err)); - } - if(compat_connect(a, addr, addrlen) < 0) { - int err = errno; - close(a); - close(b); - throw std::runtime_error(std::string("connect (video): ") + strerror(err)); - } - mung_sockaddr(addr, addrlen); - if(compat_connect(b, addr, addrlen) < 0) { - int err = errno; - close(a); - close(b); - throw std::runtime_error(std::string("connect (audio): ") + strerror(err)); - } - std::cerr << "Routing video to socket " << a << std::endl; - std::cerr << "Routing audio to socket " << b << std::endl; - - return std::make_pair(a, b); - } - - std::pair get_sockets(const std::string& name) - { - struct addrinfo hints; - struct addrinfo* ainfo; - bool real = false; - int r; - std::string node, service, tmp = name; - size_t s; - struct sockaddr_un uaddr; - if(name[0] == '/' || name[0] == '@') { - //Fake a unix-domain. - if(name.length() >= sizeof(sockaddr_un) - offsetof(sockaddr_un, sun_path) - 1) - throw std::runtime_error("Path too long for filesystem socket"); - size_t namelen = offsetof(struct sockaddr_un, sun_path) + name.length(); - uaddr.sun_family = AF_UNIX; - strcpy(uaddr.sun_path, name.c_str()); - if(name[0] == '@') - uaddr.sun_path[0] = 0; //Mark as abstract namespace socket. - ainfo = &hints; - ainfo->ai_flags = 0; - ainfo->ai_family = AF_UNIX; - ainfo->ai_socktype = SOCK_STREAM; - ainfo->ai_protocol = 0; - ainfo->ai_addrlen = (name[0] == '@') ? namelen : sizeof(sockaddr_un), - ainfo->ai_addr = reinterpret_cast(&uaddr); - ainfo->ai_canonname = NULL; - ainfo->ai_next = NULL; - goto establish; - } - //Split into address and port. - s = tmp.find_last_of(":"); - if(s >= tmp.length()) - throw std::runtime_error("Port number has to be specified"); - node = tmp.substr(0, s); - service = tmp.substr(s + 1); - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; -#ifdef AI_V4MAPPED - hints.ai_flags = AI_V4MAPPED; -#endif -#ifdef AI_ADDRCONFIG - hints.ai_flags = AI_ADDRCONFIG; -#endif - real = true; - r = getaddrinfo(node.c_str(), service.c_str(), &hints, &ainfo); - if(r < 0) - throw std::runtime_error(std::string("getaddrinfo: ") + gai_strerror(r)); -establish: - auto x = establish_connections(ainfo); - if(real) - freeaddrinfo(ainfo); - return x; - } - - class socket_output - { - public: - typedef char char_type; - typedef boost::iostreams::sink_tag category; - socket_output(int _fd) - : fd(_fd) - { - } - - void close() - { - ::close(fd); - } - - std::streamsize write(const char* s, std::streamsize n) - { - size_t w = n; - while(n > 0) { - ssize_t r = ::send(fd, s, n, 0); - if(r >= 0) { - s += r; - n -= r; - } else { //Error. - int err = errno; - messages << "Socket write error: " << strerror(err) << std::endl; - break; - } - } - return w; - } - protected: - int fd; - }; - bool tcp_dump_supported = true; -#else - std::pair get_sockets(const std::string& name) - { - throw std::runtime_error("Dumping over TCP/IP not supported"); - } - - class socket_output - { - public: - typedef char char_type; - typedef boost::iostreams::sink_tag category; - socket_output(int _fd) {} - void close() {} - std::streamsize write(const char* s, std::streamsize n) { return n; } - }; - bool tcp_dump_supported = false; -#endif - - unsigned strhash(const std::string& str) { unsigned h = 0; @@ -238,6 +28,11 @@ establish: return h; } + void deleter_fn(void* f) + { + delete reinterpret_cast(f); + } + class raw_avsnoop : public information_dispatch { public: @@ -246,12 +41,22 @@ establish: { enable_send_sound(); if(socket_mode) { - std::pair socks = get_sockets(prefix); - video = new boost::iostreams::stream(socks.first); - audio = new boost::iostreams::stream(socks.second); + socket_address videoaddr = socket_address(prefix); + socket_address audioaddr = videoaddr.next(); + deleter = socket_address::deleter(); + video = audio = NULL; + try { + video = &videoaddr.connect(); + audio = &audioaddr.connect(); + } catch(...) { + deleter(video); + deleter(audio); + throw; + } } else { video = new std::ofstream(prefix + ".video", std::ios::out | std::ios::binary); audio = new std::ofstream(prefix + ".audio", std::ios::out | std::ios::binary); + deleter = deleter_fn; } if(!*video || !*audio) throw std::runtime_error("Can't open output files"); @@ -262,8 +67,10 @@ establish: ~raw_avsnoop() throw() { - delete video; - delete audio; + if(video) + deleter(video); + if(audio) + deleter(audio); } void on_frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d) @@ -306,8 +113,8 @@ establish: void on_dump_end() { - delete video; - delete audio; + deleter(video); + deleter(audio); video = NULL; audio = NULL; } @@ -319,6 +126,7 @@ establish: private: std::ostream* audio; std::ostream* video; + void (*deleter)(void* f); bool have_dumped_frame; struct screen dscr; struct screen dscr2; @@ -336,7 +144,7 @@ establish: std::set list_submodes() throw(std::bad_alloc) { std::set x; - for(size_t i = 0; i < (tcp_dump_supported ? 2 : 1); i++) + for(size_t i = 0; i < (socket_address::supported() ? 2 : 1); i++) for(size_t j = 0; j < 2; j++) for(size_t k = 0; k < 2; k++) x.insert(std::string("") + (i ? "tcp" : "") + (j ? "bgr" : "rgb") @@ -344,9 +152,9 @@ establish: return x; } - bool wants_prefix(const std::string& mode) throw() + unsigned mode_details(const std::string& mode) throw() { - return true; + return IS_TCP(strhash(mode)) ? target_type_special : target_type_prefix; } std::string name() throw(std::bad_alloc) diff --git a/src/video/sdmp.cpp b/src/video/sdmp.cpp index 82644ce1..00142246 100644 --- a/src/video/sdmp.cpp +++ b/src/video/sdmp.cpp @@ -4,6 +4,7 @@ #include "core/dispatch.hpp" #include "interface/core.hpp" #include "library/serialization.hpp" +#include "video/tcp.hpp" #include #include @@ -22,25 +23,38 @@ namespace { + void deleter_fn(void* f) + { + delete reinterpret_cast(f); + } + class sdmp_avsnoop : public information_dispatch { public: - sdmp_avsnoop(const std::string& prefix, bool ssflag) throw(std::bad_alloc) + sdmp_avsnoop(const std::string& prefix, const std::string& mode) throw(std::bad_alloc, + std::runtime_error) : information_dispatch("dump-sdmp") { enable_send_sound(); oprefix = prefix; - sdump_ss = ssflag; + sdump_ss = (mode != "ms"); ssize = 0; next_seq = 0; - sdump_iopen = false; + dumped_pic = false; + if(mode == "tcp") { + out = &(socket_address(prefix).connect()); + deleter = socket_address::deleter(); + } else { + out = NULL; + deleter = deleter_fn; + } } ~sdmp_avsnoop() throw() { try { - if(sdump_iopen) - out.close(); + if(out) + deleter(out); } catch(...) { } } @@ -53,10 +67,10 @@ namespace flags |= (overscan ? SDUMP_FLAG_OVERSCAN : 0); flags |= (region == VIDEO_REGION_PAL ? SDUMP_FLAG_PAL : 0); unsigned char tbuffer[2049]; - if(!sdump_iopen || (ssize > CUTOFF && !sdump_ss)) { + if(!out || (ssize > CUTOFF && !sdump_ss)) { std::cerr << "Starting new segment" << std::endl; - if(sdump_iopen) - out.close(); + if(out) + deleter(out); std::ostringstream str; if(sdump_ss) str << oprefix; @@ -64,10 +78,9 @@ namespace str << oprefix << "_" << std::setw(4) << std::setfill('0') << (next_seq++) << ".sdmp"; std::string str2 = str.str(); - out.open(str2.c_str(), std::ios::out | std::ios::binary); - if(!out) + out = new std::ofstream(str2.c_str(), std::ios::out | std::ios::binary); + if(!*out) throw std::runtime_error("Failed to open '" + str2 + "'"); - sdump_iopen = true; write32ube(tbuffer, 0x53444D50U); write32ube(tbuffer + 4, emucore_get_video_rate().first); write32ube(tbuffer + 8, emucore_get_audio_rate().first); @@ -76,35 +89,36 @@ namespace throw std::runtime_error("Failed to write header to '" + str2 + "'"); ssize = 12; } + dumped_pic = true; tbuffer[0] = flags; for(unsigned i = 0; i < 512; i++) { for(unsigned j = 0; j < 512; j++) write32ube(tbuffer + (4 * j + 1), raw[512 * i + j]); - out.write(reinterpret_cast(tbuffer + (i ? 1 : 0)), i ? 2048 : 2049); + out->write(reinterpret_cast(tbuffer + (i ? 1 : 0)), i ? 2048 : 2049); } - if(!out) + if(!*out) throw std::runtime_error("Failed to write frame"); ssize += 1048577; } void on_sample(short l, short r) { - if(!sdump_iopen) + if(!out || !dumped_pic) return; unsigned char pkt[5]; pkt[0] = 16; write16sbe(pkt + 1, l); write16sbe(pkt + 3, r); - out.write(reinterpret_cast(pkt), 5); - if(!out) + out->write(reinterpret_cast(pkt), 5); + if(!*out) throw std::runtime_error("Failed to write sample"); ssize += 5; } void on_dump_end() { - if(sdump_iopen) - out.close(); + deleter(out); + out = NULL; } bool get_dumper_flag() throw() @@ -114,10 +128,11 @@ namespace private: std::string oprefix; bool sdump_ss; + bool dumped_pic; uint64_t ssize; uint64_t next_seq; - bool sdump_iopen; - std::ofstream out; + void (*deleter)(void* f); + std::ostream* out; }; sdmp_avsnoop* vid_dumper; @@ -132,12 +147,19 @@ namespace std::set x; x.insert("ss"); x.insert("ms"); + x.insert("tcp"); return x; } - bool wants_prefix(const std::string& mode) throw() + unsigned mode_details(const std::string& mode) throw() { - return (mode != "ss"); + if(mode == "ss") + return target_type_file; + if(mode == "ms") + return target_type_prefix; + if(mode == "tcp") + return target_type_special; + return target_type_mask; } std::string name() throw(std::bad_alloc) @@ -147,7 +169,13 @@ namespace std::string modename(const std::string& mode) throw(std::bad_alloc) { - return (mode == "ss" ? "Single-Segment" : "Multi-Segment"); + if(mode == "ss") + return "Single-Segment"; + if(mode == "ms") + return "Multi-Segment"; + if(mode == "tcp") + return "over TCP/IP"; + return "What?"; } bool busy() @@ -158,16 +186,12 @@ namespace void start(const std::string& mode, const std::string& prefix) throw(std::bad_alloc, std::runtime_error) { - if(prefix == "") { - if(mode == "ss") - throw std::runtime_error("Expected filename"); - else - throw std::runtime_error("Expected prefix"); - } + if(prefix == "") + throw std::runtime_error("Expected target"); if(vid_dumper) throw std::runtime_error("SDMP Dump already in progress"); try { - vid_dumper = new sdmp_avsnoop(prefix, mode == "ss"); + vid_dumper = new sdmp_avsnoop(prefix, mode); } catch(std::bad_alloc& e) { throw; } catch(std::exception& e) { @@ -175,10 +199,7 @@ namespace x << "Error starting SDMP dump: " << e.what(); throw std::runtime_error(x.str()); } - if(mode == "ss") - messages << "Dumping SDMP (SS) to " << prefix << std::endl; - else - messages << "Dumping SDMP to " << prefix << std::endl; + messages << "Dumping SDMP (" << mode << ") to " << prefix << std::endl; information_dispatch::do_dumper_update(); } diff --git a/src/video/tcp.cpp b/src/video/tcp.cpp new file mode 100644 index 00000000..14397e83 --- /dev/null +++ b/src/video/tcp.cpp @@ -0,0 +1,251 @@ +#include "video/tcp.hpp" + +#ifdef NO_TCP_SOCKETS + +namespace +{ + void deleter_fn(void* f) + { + } +} + +socket_address::socket_address(const std::string& spec) +{ + throw std::runtime_error("TCP/IP support not compiled in"); +} + +socket_address socket_address::next() +{ +} + +std::ostream& socket_address::connect() +{ + throw std::runtime_error("TCP/IP support not compiled in"); +} + +bool socket_address::supported() +{ + return false; +} + +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_WIN32) || defined(_WIN64) +//Why the fuck does windows have nonstandard socket API??? +#define _WIN32_WINNT 0x0501 +#include +#include +struct sockaddr_un { int sun_family; char sun_path[108]; }; +#else +#include +#include +#include +#endif +#include + +namespace +{ + class socket_output + { + public: + typedef char char_type; + typedef struct : public boost::iostreams::sink_tag, boost::iostreams::closable_tag {} category; + socket_output(int _fd) + : fd(_fd) + { + broken = false; + } + + void close() + { + ::close(fd); + } + + std::streamsize write(const char* s, std::streamsize n) + { + if(broken) + return n; + size_t w = n; + while(n > 0) { + ssize_t r = ::send(fd, s, n, 0); + if(r >= 0) { + s += r; + n -= r; + } else if(errno == EPIPE) { + std::cerr << "The other end of socket went away" << std::endl; + broken = true; + n = 0; + break; + } else { //Error. + int err = errno; + std::cerr << "Socket write error: " << strerror(err) << std::endl; + n = 0; + break; + } + } + return w; + } + protected: + int fd; + bool broken; + }; + + void deleter_fn(void* f) + { + delete reinterpret_cast*>(f); + } +} + +socket_address::socket_address(const std::string& name) +{ + struct addrinfo hints; + struct addrinfo* ainfo; + bool real = false; + int r; + std::string node, service, tmp = name; + size_t s; + struct sockaddr_un uaddr; + if(name[0] == '/' || name[0] == '@') { + //Fake a unix-domain. + if(name.length() >= sizeof(sockaddr_un) - offsetof(sockaddr_un, sun_path) - 1) + throw std::runtime_error("Path too long for filesystem socket"); + size_t namelen = offsetof(struct sockaddr_un, sun_path) + name.length(); + uaddr.sun_family = AF_UNIX; + strcpy(uaddr.sun_path, name.c_str()); + if(name[0] == '@') + uaddr.sun_path[0] = 0; //Mark as abstract namespace socket. + family = AF_UNIX; + socktype = SOCK_STREAM; + protocol = 0; + memory.resize((name[0] == '@') ? namelen : sizeof(sockaddr_un)); + memcpy(&memory[0], &uaddr, memory.size()); + return; + } + //Split into address and port. + s = tmp.find_last_of(":"); + if(s >= tmp.length()) + throw std::runtime_error("Port number has to be specified"); + node = tmp.substr(0, s); + service = tmp.substr(s + 1); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; +#ifdef AI_V4MAPPED + hints.ai_flags = AI_V4MAPPED; +#endif +#ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; +#endif + real = true; + r = getaddrinfo(node.c_str(), service.c_str(), &hints, &ainfo); + if(r < 0) + throw std::runtime_error(std::string("getaddrinfo: ") + gai_strerror(r)); +establish: + family = ainfo->ai_family; + socktype = ainfo->ai_socktype; + protocol = ainfo->ai_protocol; + try { + memory.resize(ainfo->ai_addrlen); + memcpy(&memory[0], ainfo->ai_addr, ainfo->ai_addrlen); + } catch(...) { + freeaddrinfo(ainfo); + throw; + } + freeaddrinfo(ainfo); +} + +socket_address socket_address::next() +{ + std::vector newaddr = memory; + struct sockaddr* addr = reinterpret_cast(&newaddr[0]); + socklen_t addrlen = memory.size(); + switch(addr->sa_family) { + case AF_INET: { //IPv4 + struct sockaddr_in* _addr = (struct sockaddr_in*)addr; + _addr->sin_port = htons(htons(_addr->sin_port) + 1); + break; + } + case AF_INET6: { //IPv6 + struct sockaddr_in6* _addr = (struct sockaddr_in6*)addr; + _addr->sin6_port = htons(htons(_addr->sin6_port) + 1); + break; + } + case AF_UNIX: { //Unix domain sockets. + struct sockaddr_un* _addr = (struct sockaddr_un*)addr; + const char* b1 = (char*)_addr; + const char* b2 = (char*)&_addr->sun_path; + size_t maxpath = addrlen - (b2 - b1); + for(size_t i = 0; i < maxpath; i++) + if(i && !_addr->sun_path[i]) { + maxpath = i; + break; + } + if(!maxpath) + throw std::runtime_error("Eh, empty unix domain socket path?"); + _addr->sun_path[maxpath - 1]++; + break; + } + default: + throw std::runtime_error("This address family is not supported, sorry."); + } + socket_address n(family, socktype, protocol); + n.memory = newaddr; + return n; +} + +std::ostream& socket_address::connect() +{ + int a = socket(family, socktype, protocol); + if(a < 0) { + int err = errno; + throw std::runtime_error(std::string("socket: ") + strerror(err)); + } + int r; + struct sockaddr* addr = reinterpret_cast(&memory[0]); + socklen_t addrlen = memory.size(); +#if defined(_WIN32) || defined(_WIN64) + r = ::connect(a, addr, addrlen) ? -1 : 0; +#else + r = ::connect(a, addr, addrlen); +#endif + if(r < 0) { + int err = errno; + ::close(a); + throw std::runtime_error(std::string("connect: ") + strerror(err)); + } + try { + return *new boost::iostreams::stream(a); + } catch(...) { + ::close(a); + throw; + } +} + +bool socket_address::supported() +{ + return true; +} + +#endif + +deleter_fn_t socket_address::deleter() +{ + return deleter_fn; +} + + +socket_address::socket_address(int f, int st, int p) +{ + family = f; + socktype = st; + protocol = p; +}