bsnes/ruby/input/joypad/directinput.cpp
Tim Allen 4129630d97 Update to v103r15 release.
byuu says:

Changelog:

  - ruby: rewrote the API interfaces for Video, Audio, Input
  - ruby/audio: can now select the number of output channels (not useful
    to higan, sorry)
  - ruby/asio: various improvements
  - tomoko: audio settings panel can now select separate audio devices
    (for ASIO, OSS so far)
  - tomoko: audio settings panel frequency and latency lists are
    dynamically populated now

Note: due to the ruby API rewrite, most drivers will not compile. Right
now, the following work:

  - video: Direct3D, XShm
  - audio: ASIO, OSS
  - input: Windows, SDL, Xlib

It takes a really long time to rewrite these (six hours to do the
above), so it's going to be a while before we're back at 100%
functionality again.

Errata:

  - ASIO needs device(), setDevice()
  - need to call setDevice() at program startup to populate
    frequency/latency settings properly
  - changing the device and/or frequency needs to update the emulator
    resampler rates

The really hard part is going to be the last one: the only way to change
the emulator frequency is to flush all the audio streams and then
recompute all the coefficients for the resamplers. If this is called
during emulation, all audio streams will be erased and thus no sound
will be output. I'll most likely be forced to simply ignore
device/frequency changes until the user loads another game. It is at
least possible to toggle the latency dynamically.
2017-07-17 15:11:18 +10:00

219 lines
7.9 KiB
C++

#ifndef RUBY_INPUT_JOYPAD_DIRECTINPUT
#define RUBY_INPUT_JOYPAD_DIRECTINPUT
auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL;
auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL;
struct InputJoypadDirectInput {
Input& input;
InputJoypadDirectInput(Input& input) : input(input) {}
struct Joypad {
shared_pointer<HID::Joypad> hid{new HID::Joypad};
LPDIRECTINPUTDEVICE8 device = nullptr;
LPDIRECTINPUTEFFECT effect = nullptr;
uint32_t pathID = 0;
uint16_t vendorID = 0;
uint16_t productID = 0;
bool isXInputDevice = false;
};
vector<Joypad> joypads;
uintptr_t handle = 0;
LPDIRECTINPUT8 context = nullptr;
LPDIRECTINPUTDEVICE8 device = nullptr;
bool xinputAvailable = false;
unsigned effects = 0;
auto assign(shared_pointer<HID::Joypad> hid, unsigned groupID, unsigned inputID, int16_t value) -> void {
auto& group = hid->group(groupID);
if(group.input(inputID).value() == value) return;
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
group.input(inputID).setValue(value);
}
auto poll(vector<shared_pointer<HID::Device>>& devices) -> void {
for(auto& jp : joypads) {
if(FAILED(jp.device->Poll())) jp.device->Acquire();
DIJOYSTATE2 state;
if(FAILED(jp.device->GetDeviceState(sizeof(DIJOYSTATE2), &state))) continue;
for(unsigned n = 0; n < 4; n++) {
assign(jp.hid, HID::Joypad::GroupID::Axis, 0, state.lX);
assign(jp.hid, HID::Joypad::GroupID::Axis, 1, state.lY);
assign(jp.hid, HID::Joypad::GroupID::Axis, 2, state.lZ);
assign(jp.hid, HID::Joypad::GroupID::Axis, 3, state.lRx);
assign(jp.hid, HID::Joypad::GroupID::Axis, 4, state.lRy);
assign(jp.hid, HID::Joypad::GroupID::Axis, 5, state.lRz);
unsigned pov = state.rgdwPOV[n];
int16_t xaxis = 0;
int16_t yaxis = 0;
if(pov < 36000) {
if(pov >= 31500 || pov <= 4500) yaxis = -32768;
if(pov >= 4500 && pov <= 13500) xaxis = +32767;
if(pov >= 13500 && pov <= 22500) yaxis = +32767;
if(pov >= 22500 && pov <= 31500) xaxis = -32768;
}
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 0, xaxis);
assign(jp.hid, HID::Joypad::GroupID::Hat, n * 2 + 1, yaxis);
}
for(unsigned n = 0; n < 128; n++) {
assign(jp.hid, HID::Joypad::GroupID::Button, n, (bool)state.rgbButtons[n]);
}
devices.append(jp.hid);
}
}
auto rumble(uint64_t id, bool enable) -> bool {
for(auto& jp : joypads) {
if(jp.hid->id() != id) continue;
if(jp.effect == nullptr) continue;
if(enable) jp.effect->Start(1, 0);
else jp.effect->Stop();
return true;
}
return false;
}
auto initialize(uintptr handle, LPDIRECTINPUT8 context, bool xinputAvailable) -> bool {
if(!handle) return false;
this->handle = handle;
this->context = context;
this->xinputAvailable = xinputAvailable;
context->EnumDevices(DI8DEVCLASS_GAMECTRL, DirectInput_EnumJoypadsCallback, (void*)this, DIEDFL_ATTACHEDONLY);
return true;
}
auto terminate() -> void {
for(auto& jp : joypads) {
jp.device->Unacquire();
if(jp.effect) jp.effect->Release();
jp.device->Release();
}
joypads.reset();
context = nullptr;
}
auto initJoypad(const DIDEVICEINSTANCE* instance) -> bool {
Joypad jp;
jp.vendorID = instance->guidProduct.Data1 >> 0;
jp.productID = instance->guidProduct.Data1 >> 16;
jp.isXInputDevice = false;
if(auto device = rawinput.find(jp.vendorID, jp.productID)) {
jp.isXInputDevice = device().isXInputDevice;
}
//Microsoft has intentionally imposed artificial restrictions on XInput devices when used with DirectInput
//a) the two triggers are merged into a single axis, making uniquely distinguishing them impossible
//b) rumble support is not exposed
//thus, it's always preferred to let the XInput driver handle these joypads
//but if the driver is not available (XInput 1.3 does not ship with stock Windows XP), fall back on DirectInput
if(jp.isXInputDevice && xinputAvailable) return DIENUM_CONTINUE;
if(FAILED(context->CreateDevice(instance->guidInstance, &device, 0))) return DIENUM_CONTINUE;
jp.device = device;
device->SetDataFormat(&c_dfDIJoystick2);
device->SetCooperativeLevel((HWND)handle, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
effects = 0;
device->EnumObjects(DirectInput_EnumJoypadAxesCallback, (void*)this, DIDFT_ABSAXIS);
device->EnumObjects(DirectInput_EnumJoypadEffectsCallback, (void*)this, DIDFT_FFACTUATOR);
jp.hid->setRumble(effects > 0);
DIPROPGUIDANDPATH property;
memset(&property, 0, sizeof(DIPROPGUIDANDPATH));
property.diph.dwSize = sizeof(DIPROPGUIDANDPATH);
property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
property.diph.dwObj = 0;
property.diph.dwHow = DIPH_DEVICE;
device->GetProperty(DIPROP_GUIDANDPATH, &property.diph);
string devicePath = (const char*)utf8_t(property.wszPath);
jp.pathID = Hash::CRC32(devicePath.data(), devicePath.size()).value();
jp.hid->setID((uint64_t)jp.pathID << 32 | jp.vendorID << 16 | jp.productID << 0);
if(jp.hid->rumble()) {
//disable auto-centering spring for rumble support
DIPROPDWORD property;
memset(&property, 0, sizeof(DIPROPDWORD));
property.diph.dwSize = sizeof(DIPROPDWORD);
property.diph.dwHeaderSize = sizeof(DIPROPHEADER);
property.diph.dwObj = 0;
property.diph.dwHow = DIPH_DEVICE;
property.dwData = false;
device->SetProperty(DIPROP_AUTOCENTER, &property.diph);
DWORD dwAxes[2] = {(DWORD)DIJOFS_X, (DWORD)DIJOFS_Y};
LONG lDirection[2] = {0, 0};
DICONSTANTFORCE force;
force.lMagnitude = DI_FFNOMINALMAX; //full force
DIEFFECT effect;
memset(&effect, 0, sizeof(DIEFFECT));
effect.dwSize = sizeof(DIEFFECT);
effect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
effect.dwDuration = INFINITE;
effect.dwSamplePeriod = 0;
effect.dwGain = DI_FFNOMINALMAX;
effect.dwTriggerButton = DIEB_NOTRIGGER;
effect.dwTriggerRepeatInterval = 0;
effect.cAxes = 2;
effect.rgdwAxes = dwAxes;
effect.rglDirection = lDirection;
effect.lpEnvelope = 0;
effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
effect.lpvTypeSpecificParams = &force;
effect.dwStartDelay = 0;
device->CreateEffect(GUID_ConstantForce, &effect, &jp.effect, NULL);
}
for(unsigned n = 0; n < 6; n++) jp.hid->axes().append(n);
for(unsigned n = 0; n < 8; n++) jp.hid->hats().append(n);
for(unsigned n = 0; n < 128; n++) jp.hid->buttons().append(n);
joypads.append(jp);
return DIENUM_CONTINUE;
}
auto initAxis(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
DIPROPRANGE range;
memset(&range, 0, sizeof(DIPROPRANGE));
range.diph.dwSize = sizeof(DIPROPRANGE);
range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
range.diph.dwHow = DIPH_BYID;
range.diph.dwObj = instance->dwType;
range.lMin = -32768;
range.lMax = +32767;
device->SetProperty(DIPROP_RANGE, &range.diph);
return DIENUM_CONTINUE;
}
auto initEffect(const DIDEVICEOBJECTINSTANCE* instance) -> bool {
effects++;
return DIENUM_CONTINUE;
}
};
auto CALLBACK DirectInput_EnumJoypadsCallback(const DIDEVICEINSTANCE* instance, void* p) -> BOOL {
return ((InputJoypadDirectInput*)p)->initJoypad(instance);
}
auto CALLBACK DirectInput_EnumJoypadAxesCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
return ((InputJoypadDirectInput*)p)->initAxis(instance);
}
auto CALLBACK DirectInput_EnumJoypadEffectsCallback(const DIDEVICEOBJECTINSTANCE* instance, void* p) -> BOOL {
return ((InputJoypadDirectInput*)p)->initEffect(instance);
}
#endif