idzio/di.c: Add positional shifter support

This commit is contained in:
Tau 2019-09-29 16:37:18 -04:00
parent bef6e9b4aa
commit 2214374bbd
3 changed files with 153 additions and 127 deletions

View File

@ -2,12 +2,17 @@
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <wchar.h>
#include "idzio/config.h" #include "idzio/config.h"
void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename) void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename)
{ {
wchar_t key[8];
int i;
assert(cfg != NULL); assert(cfg != NULL);
assert(filename != NULL); assert(filename != NULL);
@ -19,6 +24,14 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename)
_countof(cfg->device_name), _countof(cfg->device_name),
filename); filename);
GetPrivateProfileStringW(
L"dinput",
L"shifterName",
L"",
cfg->shifter_name,
_countof(cfg->shifter_name),
filename);
GetPrivateProfileStringW( GetPrivateProfileStringW(
L"dinput", L"dinput",
L"brakeAxis", L"brakeAxis",
@ -39,6 +52,11 @@ void idz_di_config_load(struct idz_di_config *cfg, const wchar_t *filename)
cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename); cfg->view_chg = GetPrivateProfileIntW(L"dinput", L"viewChg", 0, filename);
cfg->shift_dn = GetPrivateProfileIntW(L"dinput", L"shiftDn", 0, filename); cfg->shift_dn = GetPrivateProfileIntW(L"dinput", L"shiftDn", 0, filename);
cfg->shift_up = GetPrivateProfileIntW(L"dinput", L"shiftUp", 0, filename); cfg->shift_up = GetPrivateProfileIntW(L"dinput", L"shiftUp", 0, filename);
for (i = 0 ; i < 6 ; i++) {
swprintf_s(key, _countof(key), L"gear%i", i + 1);
cfg->gear[i] = GetPrivateProfileIntW(L"dinput", key, i + 1, filename);
}
} }
void idz_io_config_load(struct idz_io_config *cfg, const wchar_t *filename) void idz_io_config_load(struct idz_io_config *cfg, const wchar_t *filename)

View File

@ -10,12 +10,14 @@ struct idz_shifter_config {
struct idz_di_config { struct idz_di_config {
wchar_t device_name[64]; wchar_t device_name[64];
wchar_t shifter_name[64];
wchar_t brake_axis[16]; wchar_t brake_axis[16];
wchar_t accel_axis[16]; wchar_t accel_axis[16];
uint8_t start; uint8_t start;
uint8_t view_chg; uint8_t view_chg;
uint8_t shift_dn; uint8_t shift_dn;
uint8_t shift_up; uint8_t shift_up;
uint8_t gear[6];
}; };
struct idz_io_config { struct idz_io_config {

View File

@ -8,6 +8,7 @@
#include "idzio/backend.h" #include "idzio/backend.h"
#include "idzio/config.h" #include "idzio/config.h"
#include "idzio/di.h" #include "idzio/di.h"
#include "idzio/di-dev.h"
#include "idzio/idzio.h" #include "idzio/idzio.h"
#include "idzio/shifter.h" #include "idzio/shifter.h"
#include "idzio/wnd.h" #include "idzio/wnd.h"
@ -20,19 +21,17 @@ struct idz_di_axis {
size_t off; size_t off;
}; };
union idz_di_state {
DIJOYSTATE st;
uint8_t bytes[sizeof(DIJOYSTATE)];
};
static HRESULT idz_di_config_apply(const struct idz_di_config *cfg); static HRESULT idz_di_config_apply(const struct idz_di_config *cfg);
static const struct idz_di_axis *idz_di_get_axis(const wchar_t *name); static const struct idz_di_axis *idz_di_get_axis(const wchar_t *name);
static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx); static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx);
static void idz_di_try_fx(void); static BOOL idz_di_enum_callback_shifter(
static HRESULT idz_di_poll(union idz_di_state *state); const DIDEVICEINSTANCEW *dev,
void *ctx);
static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out); static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out);
static uint8_t idz_di_decode_pov(DWORD pov); static uint8_t idz_di_decode_pov(DWORD pov);
static void idz_di_jvs_read_shifter(uint8_t *gear); static void idz_di_jvs_read_shifter(uint8_t *gear);
static void idz_di_jvs_read_shifter_pos(uint8_t *gear);
static void idz_di_jvs_read_shifter_virt(uint8_t *gear);
static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out); static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out);
static const struct idz_di_axis idz_di_axes[] = { static const struct idz_di_axis idz_di_axes[] = {
@ -56,6 +55,7 @@ static const struct idz_io_backend idz_di_backend = {
static HWND idz_di_wnd; static HWND idz_di_wnd;
static IDirectInput8W *idz_di_api; static IDirectInput8W *idz_di_api;
static IDirectInputDevice8W *idz_di_dev; static IDirectInputDevice8W *idz_di_dev;
static IDirectInputDevice8W *idz_di_shifter;
static IDirectInputEffect *idz_di_fx; static IDirectInputEffect *idz_di_fx;
static size_t idz_di_off_brake; static size_t idz_di_off_brake;
static size_t idz_di_off_accel; static size_t idz_di_off_accel;
@ -63,6 +63,7 @@ static uint8_t idz_di_shift_dn;
static uint8_t idz_di_shift_up; static uint8_t idz_di_shift_up;
static uint8_t idz_di_view_chg; static uint8_t idz_di_view_chg;
static uint8_t idz_di_start; static uint8_t idz_di_start;
static uint8_t idz_di_gear[6];
HRESULT idz_di_init( HRESULT idz_di_init(
const struct idz_di_config *cfg, const struct idz_di_config *cfg,
@ -109,7 +110,7 @@ HRESULT idz_di_init(
if (dinput8 == NULL) { if (dinput8 == NULL) {
hr = HRESULT_FROM_WIN32(GetLastError()); hr = HRESULT_FROM_WIN32(GetLastError());
dprintf("Wheel: LoadLibrary failed: %08x\n", (int) hr); dprintf("DirectInput: LoadLibrary failed: %08x\n", (int) hr);
return hr; return hr;
} }
@ -117,7 +118,7 @@ HRESULT idz_di_init(
api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create"); api_entry = (void *) GetProcAddress(dinput8, "DirectInput8Create");
if (api_entry == NULL) { if (api_entry == NULL) {
dprintf("Wheel: GetProcAddress failed\n"); dprintf("DirectInput: GetProcAddress failed\n");
return E_FAIL; return E_FAIL;
} }
@ -130,7 +131,7 @@ HRESULT idz_di_init(
NULL); NULL);
if (FAILED(hr)) { if (FAILED(hr)) {
dprintf("Wheel: API create failed: %08x\n", (int) hr); dprintf("DirectInput: API create failed: %08x\n", (int) hr);
return hr; return hr;
} }
@ -143,7 +144,7 @@ HRESULT idz_di_init(
DIEDFL_ATTACHEDONLY); DIEDFL_ATTACHEDONLY);
if (FAILED(hr)) { if (FAILED(hr)) {
dprintf("Wheel: EnumDevices failed: %08x\n", (int) hr); dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr; return hr;
} }
@ -154,35 +155,43 @@ HRESULT idz_di_init(
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
} }
hr = IDirectInputDevice8_SetCooperativeLevel( hr = idz_di_dev_start(idz_di_dev, idz_di_wnd);
idz_di_dev,
idz_di_wnd,
DISCL_BACKGROUND | DISCL_EXCLUSIVE);
if (FAILED(hr)) { if (FAILED(hr)) {
dprintf("Wheel: SetCooperativeLevel failed: %08x\n", (int) hr);
return hr; return hr;
} }
hr = IDirectInputDevice8_SetDataFormat(idz_di_dev, &c_dfDIJoystick); idz_di_dev_start_fx(idz_di_dev, &idz_di_fx);
if (FAILED(hr)) { if (cfg->shifter_name[0] != L'\0') {
dprintf("Wheel: SetDataFormat failed: %08x\n", (int) hr); hr = IDirectInput8_EnumDevices(
idz_di_api,
DI8DEVCLASS_GAMECTRL,
idz_di_enum_callback_shifter,
(void *) cfg,
DIEDFL_ATTACHEDONLY);
return hr; if (FAILED(hr)) {
dprintf("DirectInput: EnumDevices failed: %08x\n", (int) hr);
return hr;
}
if (idz_di_dev == NULL) {
dprintf("Shifter: Controller not found\n");
return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
}
hr = idz_di_dev_start(idz_di_shifter, idz_di_wnd);
if (FAILED(hr)) {
return hr;
}
} }
hr = IDirectInputDevice8_Acquire(idz_di_dev); dprintf("DirectInput: Controller initialized\n");
if (FAILED(hr)) {
dprintf("Wheel: Acquire failed: %08x\n", (int) hr);
return hr;
}
dprintf("Wheel: DirectInput initialized\n");
idz_di_try_fx();
*backend = &idz_di_backend; *backend = &idz_di_backend;
return S_OK; return S_OK;
@ -192,6 +201,7 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg)
{ {
const struct idz_di_axis *brake_axis; const struct idz_di_axis *brake_axis;
const struct idz_di_axis *accel_axis; const struct idz_di_axis *accel_axis;
int i;
brake_axis = idz_di_get_axis(cfg->brake_axis); brake_axis = idz_di_get_axis(cfg->brake_axis);
accel_axis = idz_di_get_axis(cfg->accel_axis); accel_axis = idz_di_get_axis(cfg->accel_axis);
@ -232,6 +242,16 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg)
return E_INVALIDARG; return E_INVALIDARG;
} }
for (i = 0 ; i < 6 ; i++) {
if (cfg->gear[i] > 32) {
dprintf("Shifter: Invalid gear %i button: %i\n",
i + 1,
cfg->gear[i]);
return E_INVALIDARG;
}
}
/* Print some debug output to make sure config works... */ /* Print some debug output to make sure config works... */
dprintf("Wheel: --- Begin configuration ---\n"); dprintf("Wheel: --- Begin configuration ---\n");
@ -245,6 +265,20 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg)
dprintf("Wheel: Shift Up button . . : %i\n", cfg->shift_up); dprintf("Wheel: Shift Up button . . : %i\n", cfg->shift_up);
dprintf("Wheel: --- End configuration ---\n"); dprintf("Wheel: --- End configuration ---\n");
if (cfg->shifter_name[0] != L'\0') {
dprintf("Shifter: --- Begin configuration ---\n");
dprintf("Shifter: Device name . . . : Contains \"%S\"\n",
cfg->shifter_name);
dprintf("Shifter: Gear buttons . . : %i %i %i %i %i %i\n",
cfg->gear[0],
cfg->gear[1],
cfg->gear[2],
cfg->gear[3],
cfg->gear[4],
cfg->gear[5]);
dprintf("Shifter: --- End configuration ---\n");
}
idz_di_off_brake = accel_axis->off; idz_di_off_brake = accel_axis->off;
idz_di_off_accel = brake_axis->off; idz_di_off_accel = brake_axis->off;
idz_di_start = cfg->start; idz_di_start = cfg->start;
@ -252,6 +286,10 @@ static HRESULT idz_di_config_apply(const struct idz_di_config *cfg)
idz_di_shift_dn = cfg->shift_dn; idz_di_shift_dn = cfg->shift_dn;
idz_di_shift_up = cfg->shift_up; idz_di_shift_up = cfg->shift_up;
for (i = 0 ; i < 6 ; i++) {
idz_di_gear[i] = cfg->gear[i];
}
return S_OK; return S_OK;
} }
@ -274,6 +312,7 @@ static const struct idz_di_axis *idz_di_get_axis(const wchar_t *name)
static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx) static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx)
{ {
const struct idz_di_config *cfg; const struct idz_di_config *cfg;
HRESULT hr;
cfg = ctx; cfg = ctx;
@ -283,119 +322,45 @@ static BOOL idz_di_enum_callback(const DIDEVICEINSTANCEW *dev, void *ctx)
dprintf("Wheel: Using DirectInput device \"%S\"\n", dev->tszProductName); dprintf("Wheel: Using DirectInput device \"%S\"\n", dev->tszProductName);
IDirectInput8_CreateDevice( hr = IDirectInput8_CreateDevice(
idz_di_api, idz_di_api,
&dev->guidInstance, &dev->guidInstance,
&idz_di_dev, &idz_di_dev,
NULL); NULL);
if (FAILED(hr)) {
dprintf("Wheel: CreateDevice failed: %08x\n", (int) hr);
}
return DIENUM_STOP; return DIENUM_STOP;
} }
static void idz_di_try_fx(void) static BOOL idz_di_enum_callback_shifter(
const DIDEVICEINSTANCEW *dev,
void *ctx)
{ {
/* Set up force-feedback on devices that support it. This is just a stub const struct idz_di_config *cfg;
for the time being, since we don't yet know how the serial port force
feedback protocol works.
I'm currently developing with an Xbox One Thrustmaster TMX wheel, if
we don't perform at least some perfunctory FFB initialization of this
nature (or indeed if no DirectInput application is running) then the
wheel exhibits considerable resistance, similar to that of a stationary
car. Changing cf.lMagnitude to a nonzero value does cause the wheel to
continuously turn in the given direction with the given force as one
would expect (max magnitude per DirectInput docs is +/- 10000).
Failure here is non-fatal, we log any errors and move on.
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416353(v=vs.85)
*/
DWORD axis;
LONG direction;
DIEFFECT fx;
DICONSTANTFORCE cf;
HRESULT hr; HRESULT hr;
dprintf("Wheel: Attempting to start force feedback (may take a sec)\n"); cfg = ctx;
axis = DIJOFS_X; if (wcsstr(dev->tszProductName, cfg->shifter_name) == NULL) {
direction = 0; return DIENUM_CONTINUE;
}
memset(&cf, 0, sizeof(cf)); dprintf("Shifter: Using DirectInput device \"%S\"\n", dev->tszProductName);
cf.lMagnitude = 0;
memset(&fx, 0, sizeof(fx)); hr = IDirectInput8_CreateDevice(
fx.dwSize = sizeof(fx); idz_di_api,
fx.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; &dev->guidInstance,
fx.dwDuration = INFINITE; &idz_di_shifter,
fx.dwGain = DI_FFNOMINALMAX;
fx.dwTriggerButton = DIEB_NOTRIGGER;
fx.dwTriggerRepeatInterval = INFINITE;
fx.cAxes = 1;
fx.rgdwAxes = &axis;
fx.rglDirection = &direction;
fx.cbTypeSpecificParams = sizeof(cf);
fx.lpvTypeSpecificParams = &cf;
hr = IDirectInputDevice8_CreateEffect(
idz_di_dev,
&GUID_ConstantForce,
&fx,
&idz_di_fx,
NULL); NULL);
if (FAILED(hr)) { if (FAILED(hr)) {
dprintf("Wheel: DirectInput force feedback unavailable: %08x\n", dprintf("Shifter: CreateDevice failed: %08x\n", (int) hr);
(int) hr);
return;
} }
hr = IDirectInputEffect_Start(idz_di_fx, INFINITE, 0); return DIENUM_STOP;
if (FAILED(hr)) {
dprintf("Wheel: DirectInput force feedback start failed: %08x\n",
(int) hr);
return;
}
dprintf("Wheel: Force feedback initialized and set to zero\n");
}
static HRESULT idz_di_poll(union idz_di_state *out)
{
HRESULT hr;
MSG msg;
memset(out, 0, sizeof(*out));
if (idz_di_dev == NULL) {
return E_UNEXPECTED;
}
/* Pump our dummy window's message queue just in case DirectInput or an
IHV DirectInput driver somehow relies on it */
while (PeekMessageW(&msg, idz_di_wnd, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
hr = IDirectInputDevice8_GetDeviceState(
idz_di_dev,
sizeof(out->st),
&out->st);
if (FAILED(hr)) {
dprintf("Wheel: I/O error: %08x\n", (int) hr);
}
/* JVS lacks a protocol for reporting hardware errors from poll command
responses, so this ends up returning zeroed input state instead. */
return hr;
} }
static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out) static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out)
@ -406,7 +371,7 @@ static void idz_di_jvs_read_buttons(uint8_t *gamebtn_out)
assert(gamebtn_out != NULL); assert(gamebtn_out != NULL);
hr = idz_di_poll(&state); hr = idz_di_dev_poll(idz_di_dev, idz_di_wnd, &state);
if (FAILED(hr)) { if (FAILED(hr)) {
return; return;
@ -441,6 +406,47 @@ static uint8_t idz_di_decode_pov(DWORD pov)
} }
static void idz_di_jvs_read_shifter(uint8_t *gear) static void idz_di_jvs_read_shifter(uint8_t *gear)
{
assert(gear != NULL);
if (idz_di_shifter != NULL) {
idz_di_jvs_read_shifter_pos(gear);
} else {
idz_di_jvs_read_shifter_virt(gear);
}
}
static void idz_di_jvs_read_shifter_pos(uint8_t *out)
{
union idz_di_state state;
uint8_t btn_no;
uint8_t gear;
uint8_t i;
HRESULT hr;
assert(out != NULL);
assert(idz_di_shifter != NULL);
hr = idz_di_dev_poll(idz_di_shifter, idz_di_wnd, &state);
if (FAILED(hr)) {
return;
}
gear = 0;
for (i = 0 ; i < 6 ; i++) {
btn_no = idz_di_gear[i];
if (btn_no && state.st.rgbButtons[btn_no - 1]) {
gear = i + 1;
}
}
*out = gear;
}
static void idz_di_jvs_read_shifter_virt(uint8_t *gear)
{ {
union idz_di_state state; union idz_di_state state;
bool shift_dn; bool shift_dn;
@ -449,7 +455,7 @@ static void idz_di_jvs_read_shifter(uint8_t *gear)
assert(gear != NULL); assert(gear != NULL);
hr = idz_di_poll(&state); hr = idz_di_dev_poll(idz_di_dev, idz_di_wnd, &state);
if (FAILED(hr)) { if (FAILED(hr)) {
return; return;
@ -481,7 +487,7 @@ static void idz_di_jvs_read_analogs(struct idz_io_analog_state *out)
assert(out != NULL); assert(out != NULL);
hr = idz_di_poll(&state); hr = idz_di_dev_poll(idz_di_dev, idz_di_wnd, &state);
if (FAILED(hr)) { if (FAILED(hr)) {
return; return;