Merge branch 'rr1-maint'
Conflicts: src/video/sdmp.cpp
This commit is contained in:
commit
906f3caa43
21 changed files with 1252 additions and 766 deletions
|
@ -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<std::string> 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.
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
|
|
26
include/video/tcp.hpp
Normal file
26
include/video/tcp.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef _tcp__hpp__included__
|
||||
#define _tcp__hpp__included__
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<char> memory;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -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<bool X> void render_video_hud(struct screen<X>& 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)())
|
||||
|
|
|
@ -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
|
||||
|
|
20
src/platform/win32mm/Makefile
Normal file
20
src/platform/win32mm/Makefile
Normal file
|
@ -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
|
1
src/platform/win32mm/dummy.cpp
Normal file
1
src/platform/win32mm/dummy.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
char SYMBOL_5825392786327896327896798437692738969832786;
|
216
src/platform/win32mm/joystick.cpp
Normal file
216
src/platform/win32mm/joystick.cpp
Normal file
|
@ -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 <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <regstr.h>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::set<keygroup*> keygroups;
|
||||
std::map<std::pair<unsigned, unsigned>, keygroup*> buttons;
|
||||
std::map<std::pair<unsigned, unsigned>, keygroup*> axes;
|
||||
std::map<unsigned, keygroup*> hats;
|
||||
std::map<std::pair<unsigned, unsigned>, short> lbuttons;
|
||||
std::map<std::pair<unsigned, unsigned>, short> laxes;
|
||||
std::map<unsigned, short> lhats;
|
||||
std::set<unsigned> joysticks;
|
||||
std::map<unsigned, JOYCAPS> 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";
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,292 +0,0 @@
|
|||
#include "core/keymapper.hpp"
|
||||
|
||||
#include "platform/wxwidgets/platform.hpp"
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#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 <wx/wx.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/control.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
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<wxeditor_axes_axis*> 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<int32_t>(tostdstring(a_low->GetValue()));
|
||||
mid = boost::lexical_cast<int32_t>(tostdstring(a_mid->GetValue()));
|
||||
high = boost::lexical_cast<int32_t>(tostdstring(a_high->GetValue()));
|
||||
tolerance = boost::lexical_cast<double>(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<int32_t>(tostdstring(a_low->GetValue()));
|
||||
mid = boost::lexical_cast<int32_t>(tostdstring(a_mid->GetValue()));
|
||||
high = boost::lexical_cast<int32_t>(tostdstring(a_high->GetValue()));
|
||||
tolerance = boost::lexical_cast<double>(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<std::string> 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<wxeditor_axes*>(editor)->has_axes())
|
||||
editor->ShowModal();
|
||||
else {
|
||||
wxMessageBox(_T("You don't have joysticks to configure!"), _T("Warning"), wxICON_WARNING |
|
||||
wxOK);
|
||||
}
|
||||
} catch(...) {
|
||||
}
|
||||
editor->Destroy();
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
#include "platform/wxwidgets/platform.hpp"
|
||||
#include "core/settings.hpp"
|
||||
#include "library/string.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/control.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#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();
|
||||
}
|
|
@ -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<std::string> 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(...) {
|
||||
}
|
||||
|
|
|
@ -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<uint32_t>(128));
|
||||
th = max(th, static_cast<uint32_t>(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<dumper_menu*>(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<autohold_menu*>(ahmenu = new autohold_menu(this)));
|
||||
blistener->set_autohold_menu(reinterpret_cast<autohold_menu*>(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<dumper_menu*>(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;
|
||||
};
|
||||
}
|
||||
|
|
452
src/platform/wxwidgets/settings.cpp
Normal file
452
src/platform/wxwidgets/settings.cpp
Normal file
|
@ -0,0 +1,452 @@
|
|||
#include "platform/wxwidgets/platform.hpp"
|
||||
#include "core/settings.hpp"
|
||||
#include "library/string.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/control.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <sstream>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#ifndef UINT64_C
|
||||
#define UINT64_C(val) val##ULL
|
||||
#endif
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#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<int32_t>(_low);
|
||||
if(nlow < -32768 || nlow > 32767)
|
||||
throw 42;
|
||||
bad_what = "Bad middle calibration value (range is -32768 - 32767)";
|
||||
nmid = boost::lexical_cast<int32_t>(_mid);
|
||||
if(nmid < -32768 || nmid > 32767)
|
||||
throw 42;
|
||||
bad_what = "Bad high calibration value (range is -32768 - 32767)";
|
||||
nhi = boost::lexical_cast<int32_t>(_hi);
|
||||
if(nhi < -32768 || nhi > 32767)
|
||||
throw 42;
|
||||
bad_what = "Bad tolerance (range is 0 - 1)";
|
||||
ntol = boost::lexical_cast<double>(_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<std::string, wxButton*> buttons;
|
||||
std::map<int, std::string> 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<std::string, keygroup::parameters> 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();
|
||||
}
|
|
@ -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<std::string>& cmdline, std::string& mode, std::string& prefix,
|
||||
uint64_t& length)
|
||||
{
|
||||
|
@ -171,18 +185,17 @@ namespace
|
|||
adv_dumper& _dumper = locate_dumper(dumper);
|
||||
std::set<std::string> 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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "core/dispatch.hpp"
|
||||
#include "core/settings.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
#include "video/tcp.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
|
@ -18,16 +19,27 @@ namespace
|
|||
{
|
||||
numeric_setting clevel("jmd-compression", 0, 9, 7);
|
||||
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
delete reinterpret_cast<std::ofstream*>(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<std::string> list_submodes() throw(std::bad_alloc)
|
||||
{
|
||||
std::set<std::string> 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) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "core/advdumper.hpp"
|
||||
#include "core/dispatch.hpp"
|
||||
#include "video/tcp.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
|
@ -10,29 +11,6 @@
|
|||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <zlib.h>
|
||||
#ifndef NO_TCP_SOCKETS
|
||||
#include <boost/iostreams/categories.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/iostreams/stream_buffer.hpp>
|
||||
#include <boost/iostreams/filter/symmetric.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
#include <boost/iostreams/device/back_inserter.hpp>
|
||||
#include <unistd.h>
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
//Why the fuck does windows have nonstandard socket API???
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
struct sockaddr_un { int sun_family; char sun_path[108]; };
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
#include <sys/types.h>
|
||||
#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<int, int> 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<int, int> 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<sockaddr*>(&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<int, int> 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<std::ofstream*>(f);
|
||||
}
|
||||
|
||||
class raw_avsnoop : public information_dispatch
|
||||
{
|
||||
public:
|
||||
|
@ -246,12 +41,22 @@ establish:
|
|||
{
|
||||
enable_send_sound();
|
||||
if(socket_mode) {
|
||||
std::pair<int, int> socks = get_sockets(prefix);
|
||||
video = new boost::iostreams::stream<socket_output>(socks.first);
|
||||
audio = new boost::iostreams::stream<socket_output>(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<false> dscr;
|
||||
struct screen<true> dscr2;
|
||||
|
@ -336,7 +144,7 @@ establish:
|
|||
std::set<std::string> list_submodes() throw(std::bad_alloc)
|
||||
{
|
||||
std::set<std::string> 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)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "core/dispatch.hpp"
|
||||
#include "interface/core.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
#include "video/tcp.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
|
@ -22,25 +23,38 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
void deleter_fn(void* f)
|
||||
{
|
||||
delete reinterpret_cast<std::ofstream*>(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<char*>(tbuffer + (i ? 1 : 0)), i ? 2048 : 2049);
|
||||
out->write(reinterpret_cast<char*>(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<char*>(pkt), 5);
|
||||
if(!out)
|
||||
out->write(reinterpret_cast<char*>(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<std::string> 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();
|
||||
}
|
||||
|
||||
|
|
251
src/video/tcp.cpp
Normal file
251
src/video/tcp.cpp
Normal file
|
@ -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 <boost/iostreams/categories.hpp>
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include <boost/iostreams/stream_buffer.hpp>
|
||||
#include <boost/iostreams/filter/symmetric.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
#include <boost/iostreams/device/back_inserter.hpp>
|
||||
#include <unistd.h>
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
//Why the fuck does windows have nonstandard socket API???
|
||||
#define _WIN32_WINNT 0x0501
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
struct sockaddr_un { int sun_family; char sun_path[108]; };
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/un.h>
|
||||
#endif
|
||||
#include <sys/types.h>
|
||||
|
||||
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<boost::iostreams::stream<socket_output>*>(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<char> newaddr = memory;
|
||||
struct sockaddr* addr = reinterpret_cast<struct sockaddr*>(&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<struct sockaddr*>(&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<socket_output>(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;
|
||||
}
|
Loading…
Add table
Reference in a new issue