Related Wine issue: https://bugs.winehq.org/show_bug.cgi?id=52714
I'm starting work on updating/extending Linux force feedback API and one of my goals is to expose the number and an array that contains the device's ffb-enabled axes. As I'm mainly working with USB PID driver, I already have a POC of obtaining this data.
What prompted this is that currently Wine always creates virtual joysticks with two PID ffb axes. This causes issues with [Richard Burns Rally](https://github.com/ValveSoftware/Proton/issues/6702#issuecomment-2670946977), as this game incorrectly initializes a `CONSTANT_FORCE` effect with all the FFB axes that contain `DIDOI_FFACTUATOR` flag and then trying to update `cAxes` and `rgdwAxes` values which leads to `DIERR_INVALIDPARAM`.
Now, this flag is set by wine while enumerating virtual device's axes and while `(axis index < FFB axes)`, flag is applied. This means, that every device which has FFB functionality and at least two axes, will report FFB on `X`, `Y`.
While updated API would mean a lot of steering wheels would correctly report only 1 axis, a lot of USB PID wheels still include `X` and `Y` in their `AXES_ENABLE` and `DIRECTION` usages. This, again, would lead to broken FFB (in affected games).
On Windows, there's a possibility of overwriting some joystick capabilities with registry entries, which ends up removing `DIDOI_FFACTUATOR` flag from a selected axis. This doesn't work in Wine.
This MR allows Wine to add an arbitrary number (up to `PID_AXES_MAX`) of axes to the PID descriptor, based on the value of Haptic Axes from SDL or from Linux FF api in the future (I'm working on it). Additionally, to work around RBR and other games with similar, wrong FFB handling, I introduced a [hint](https://github.com/libsdl-org/SDL/issues/12341) to SDL that allows a dynamic overwrite of the number of haptic axes. For the Linux API, I'll figure out a nice way to override when the number of axes will be exposed.
With these changes, I was able to successfully get force feedback on my Moza Racing R9 with all axes usable. With additional traces in the dinput code, I confirmed that with one axis defined in the PID descriptor, the game created a constant force effect with just one axis and `cAxes = 1`.
-- v28: Change few things around dinput/tests: Add tests for 6-axis ff joystick
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
--- dlls/winebus.sys/hid.c | 2 +- include/wine/hid.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/dlls/winebus.sys/hid.c b/dlls/winebus.sys/hid.c index 78efe6116ce..7b1bb14a385 100644 --- a/dlls/winebus.sys/hid.c +++ b/dlls/winebus.sys/hid.c @@ -503,7 +503,7 @@ struct pid_effect_update BYTE gain_percent; BYTE trigger_button; BYTE enable_bits; - UINT16 direction[2]; + UINT16 direction[PID_AXES_MAX]; };
struct pid_set_periodic diff --git a/include/wine/hid.h b/include/wine/hid.h index 8200ca9beec..9a24d2b43d0 100644 --- a/include/wine/hid.h +++ b/include/wine/hid.h @@ -239,6 +239,9 @@ struct hid_preparsed_data #define PID_USAGE_CREATE_NEW_EFFECT_REPORT ((USAGE) 0xab) #define PID_USAGE_RAM_POOL_AVAILABLE ((USAGE) 0xac)
+/* Define max supported FFB-enabled axes */ +#define PID_AXES_MAX 6 + #define IOCTL_HID_GET_WINE_RAWINPUT_HANDLE HID_BUFFER_CTL_CODE(300)
#endif /* __WINE_PARSE_H */
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
Currently, there are quite a few places which define properties like direction or axes with seemingly random numbers. It's mostly 6 but I found instances where it's not the case. Unify this to a known, common value. --- dlls/dinput/joystick_hid.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index f7f87596c82..ed9ac7b9940 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -69,8 +69,8 @@ struct pid_effect_update UINT axis_count; UINT direction_coll; UINT direction_count; - struct hid_value_caps *axis_caps[6]; - struct hid_value_caps *direction_caps[6]; + struct hid_value_caps *axis_caps[PID_AXES_MAX]; + struct hid_value_caps *direction_caps[PID_AXES_MAX]; struct hid_value_caps *duration_caps; struct hid_value_caps *gain_caps; struct hid_value_caps *sample_period_caps; @@ -221,11 +221,11 @@ struct hid_joystick_effect struct list entry; struct hid_joystick *joystick;
- DWORD axes[6]; - LONG directions[6]; + DWORD axes[PID_AXES_MAX]; + LONG directions[PID_AXES_MAX]; DICONSTANTFORCE constant_force; DIRAMPFORCE ramp_force; - DICONDITION condition[6]; + DICONDITION condition[PID_AXES_MAX]; DIENVELOPE envelope; DIPERIODIC periodic; DIEFFECT params; @@ -1852,7 +1852,7 @@ static BOOL init_pid_caps( struct dinput_device *device, UINT index, struct hid_ if (instance->wCollectionNumber == effect_update->axes_coll) { SET_REPORT_ID( effect_update ); - if (effect_update->axis_count >= 6) FIXME( "more than 6 PID axes detected\n" ); + if (effect_update->axis_count >= PID_AXES_MAX) FIXME( "more than %d PID axes detected\n", PID_AXES_MAX ); else effect_update->axis_caps[effect_update->axis_count] = caps; effect_update->axis_count++; } @@ -1861,7 +1861,7 @@ static BOOL init_pid_caps( struct dinput_device *device, UINT index, struct hid_ SET_REPORT_ID( effect_update ); caps->physical_min = 0; caps->physical_max = 35900; - if (effect_update->direction_count >= 6) FIXME( "more than 6 PID directions detected\n" ); + if (effect_update->direction_count >= PID_AXES_MAX) FIXME( "more than %d PID directions detected\n", PID_AXES_MAX ); else effect_update->direction_caps[effect_update->direction_count] = caps; effect_update->direction_count++; } @@ -2374,7 +2374,7 @@ static void convert_directions_from_spherical( const DIEFFECT *in, DIEFFECT *out static void convert_directions( const DIEFFECT *in, DIEFFECT *out ) { DWORD direction_flags = DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL; - LONG directions[6] = {0}; + LONG directions[PID_AXES_MAX] = {0}; DIEFFECT spherical = {.rglDirection = directions};
switch (in->dwFlags & direction_flags) @@ -2894,7 +2894,7 @@ static HRESULT WINAPI hid_joystick_effect_Download( IDirectInputEffect *iface ) ULONG report_len = impl->joystick->caps.OutputReportByteLength; HANDLE device = impl->joystick->device; struct hid_value_caps *caps; - LONG directions[4] = {0}; + LONG directions[PID_AXES_MAX] = {0}; DWORD i, tmp, count; DIEFFECT spherical; NTSTATUS status;
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
--- dlls/winebus.sys/unix_private.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/dlls/winebus.sys/unix_private.h b/dlls/winebus.sys/unix_private.h index f2f4e71f014..558cfe053d7 100644 --- a/dlls/winebus.sys/unix_private.h +++ b/dlls/winebus.sys/unix_private.h @@ -29,6 +29,7 @@ #include "unixlib.h"
#include "wine/list.h" +#include "wine/hid.h"
struct effect_periodic { @@ -75,9 +76,9 @@ struct effect_params UINT16 sample_period; UINT16 start_delay; BYTE trigger_button; - BOOL axis_enabled[2]; + BOOL axis_enabled[PID_AXES_MAX]; BOOL direction_enabled; - UINT16 direction[2]; + UINT16 direction[PID_AXES_MAX]; BYTE gain_percent; BYTE condition_count; /* only for periodic, constant or ramp forces */ @@ -85,7 +86,7 @@ struct effect_params union { struct effect_periodic periodic; - struct effect_condition condition[2]; + struct effect_condition condition[PID_AXES_MAX]; struct effect_constant_force constant_force; struct effect_ramp_force ramp_force; };
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
--- dlls/winebus.sys/hid.c | 1 + dlls/winebus.sys/unix_private.h | 2 ++ 2 files changed, 3 insertions(+)
diff --git a/dlls/winebus.sys/hid.c b/dlls/winebus.sys/hid.c index 7b1bb14a385..e128dbb7abe 100644 --- a/dlls/winebus.sys/hid.c +++ b/dlls/winebus.sys/hid.c @@ -1076,6 +1076,7 @@ BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT co iface->hid_physical.device_gain_report = device_gain_report; iface->hid_physical.effect_control_report = effect_control_report; iface->hid_physical.effect_update_report = effect_update_report; + iface->hid_physical.num_axes = 2;
effect_state->id = effect_state_report; effect_state->report_len = sizeof(struct pid_effect_state) + 1; diff --git a/dlls/winebus.sys/unix_private.h b/dlls/winebus.sys/unix_private.h index 558cfe053d7..637e975d068 100644 --- a/dlls/winebus.sys/unix_private.h +++ b/dlls/winebus.sys/unix_private.h @@ -184,6 +184,8 @@ struct hid_physical BYTE set_ramp_force_report;
struct hid_effect_state effect_state; + + USHORT num_axes; };
struct hid_device_state
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
--- dlls/winebus.sys/hid.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/dlls/winebus.sys/hid.c b/dlls/winebus.sys/hid.c index e128dbb7abe..1fdeb88038c 100644 --- a/dlls/winebus.sys/hid.c +++ b/dlls/winebus.sys/hid.c @@ -1187,9 +1187,13 @@ static void hid_device_set_output_report(struct unix_device *iface, HID_XFER_PAC { struct pid_effect_update *report = (struct pid_effect_update *)(packet->reportBuffer + 1); struct effect_params *params = iface->hid_physical.effect_params + report->index; + ULONG missing_direction_size, i; USAGE effect_type;
- io->Information = sizeof(*report) + 1; + /* Compute the actual size of the effect_update report */ + missing_direction_size = sizeof(report->direction[0]) * (PID_AXES_MAX - physical->num_axes); + io->Information = sizeof(*report) - missing_direction_size + 1; + if (packet->reportBufferLen < io->Information) io->Status = STATUS_BUFFER_TOO_SMALL; else if (report->type_index >= ARRAY_SIZE(iface->hid_physical.effect_types)) @@ -1205,11 +1209,17 @@ static void hid_device_set_output_report(struct unix_device *iface, HID_XFER_PAC params->start_delay = report->start_delay; params->gain_percent = report->gain_percent; params->trigger_button = report->trigger_button == 0xff ? 0 : report->trigger_button; - params->axis_enabled[0] = (report->enable_bits & 1) != 0; - params->axis_enabled[1] = (report->enable_bits & 2) != 0; - params->direction_enabled = (report->enable_bits & 4) != 0; - params->direction[0] = report->direction[0]; - params->direction[1] = report->direction[1]; + + /* Axes Enable */ + for (i = 0; i < physical->num_axes; ++i) + params->axis_enabled[i] = (report->enable_bits & (1 << i)) != 0; + + /* Direction Enable */ + params->direction_enabled = (report->enable_bits & (1 << i)) != 0; + + /* Direction */ + for (i = 0; i < physical->num_axes; ++i) + params->direction[i] = report->direction[i];
io->Status = iface->hid_vtbl->physical_effect_update(iface, report->index, params); }
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
Allow the PID descriptor building function to create up to PID_AXES_MAX FFB-enabled axes. This will allow better effect handling and support for devices with more than two FFB axis.
Devices with only one axis, can be actually presented as such. --- dlls/winebus.sys/bus_sdl.c | 2 +- dlls/winebus.sys/bus_udev.c | 5 ++- dlls/winebus.sys/hid.c | 71 ++++++++++++++++++++++++++------- dlls/winebus.sys/unix_private.h | 2 +- 4 files changed, 62 insertions(+), 18 deletions(-)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index e050742ca66..29a0d2cf8d2 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -238,7 +238,7 @@ static BOOL descriptor_add_haptic(struct sdl_device *impl, BOOL force) if (force || (impl->effect_support & SDL_HAPTIC_CONSTANT)) usages[count++] = PID_USAGE_ET_CONSTANT_FORCE; if (force || (impl->effect_support & SDL_HAPTIC_RAMP)) usages[count++] = PID_USAGE_ET_RAMP;
- if (!hid_device_add_physical(&impl->unix_device, usages, count)) + if (!hid_device_add_physical(&impl->unix_device, usages, count, 2)) return FALSE; }
diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index 561f0cdc0e4..f9646d8d6b4 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -648,7 +648,10 @@ static NTSTATUS build_report_descriptor(struct unix_device *iface, struct udev_d if (test_bit(ffbits, FF_CONSTANT)) usages[count++] = PID_USAGE_ET_CONSTANT_FORCE; if (test_bit(ffbits, FF_RAMP)) usages[count++] = PID_USAGE_ET_RAMP;
- if (!hid_device_add_physical(iface, usages, count)) + /* Hardcode 2 for now until Linux FF api exposes number of FFB axes + * (previously hardcoded number of PID axes in Wine) + */ + if (!hid_device_add_physical(iface, usages, count, 2)) return STATUS_NO_MEMORY; }
diff --git a/dlls/winebus.sys/hid.c b/dlls/winebus.sys/hid.c index 1fdeb88038c..5ac6cd06498 100644 --- a/dlls/winebus.sys/hid.c +++ b/dlls/winebus.sys/hid.c @@ -672,7 +672,7 @@ static BOOL hid_descriptor_add_set_envelope(struct unix_device *iface) return hid_report_descriptor_append(desc, template, sizeof(template)); }
-static BOOL hid_descriptor_add_set_condition(struct unix_device *iface) +static BOOL hid_descriptor_add_set_condition(struct unix_device *iface, USHORT pid_axes) { struct hid_report_descriptor *desc = &iface->hid_report_descriptor; const BYTE report_id = ++desc->next_report_id[HidP_Output]; @@ -691,8 +691,8 @@ static BOOL hid_descriptor_add_set_condition(struct unix_device *iface) OUTPUT(1, Data|Var|Abs),
USAGE(1, PID_USAGE_PARAMETER_BLOCK_OFFSET), - LOGICAL_MINIMUM(1, 0x00), - LOGICAL_MAXIMUM(1, 0x01), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, pid_axes - 1), REPORT_SIZE(1, 8), REPORT_COUNT(1, 1), OUTPUT(1, Data|Var|Abs), @@ -800,11 +800,36 @@ static BOOL hid_descriptor_add_set_ramp_force(struct unix_device *iface) return hid_report_descriptor_append(desc, template, sizeof(template)); }
-BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT count) +static BOOL hid_descriptor_add_physical_axis_enable(struct unix_device *iface, USHORT i) +{ + struct hid_report_descriptor *desc = &iface->hid_report_descriptor; + USAGE_AND_PAGE uap = iface->hid_device_state.abs_axis_usages[i]; + const BYTE template[] = + { + USAGE(4, (uap.UsagePage<<16)|uap.Usage), + }; + + return hid_report_descriptor_append(desc, template, sizeof(template)); +} + +static BOOL hid_descriptor_add_physical_direction(struct unix_device *iface, USHORT i) +{ + struct hid_report_descriptor *desc = &iface->hid_report_descriptor; + const BYTE template[] = + { + USAGE(4, (HID_USAGE_PAGE_ORDINAL<<16)|(i+1)), + }; + + return hid_report_descriptor_append(desc, template, sizeof(template)); +} + +BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT count, USHORT num_axes) { struct hid_report_descriptor *desc = &iface->hid_report_descriptor; const BYTE device_control_report = ++desc->next_report_id[HidP_Output]; struct hid_device_state *state = &iface->hid_device_state; + USHORT pid_axes = num_axes > PID_AXES_MAX ? PID_AXES_MAX : num_axes; + const BYTE device_control_header[] = { USAGE_PAGE(1, HID_USAGE_PAGE_PID), @@ -901,7 +926,7 @@ BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT co USAGE(1, PID_USAGE_EFFECT_TYPE), COLLECTION(1, Logical), }; - const BYTE effect_update_footer[] = + const BYTE effect_update_middle1[] = { LOGICAL_MINIMUM(1, 1), LOGICAL_MAXIMUM(1, count), @@ -939,30 +964,32 @@ BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT co
USAGE(1, PID_USAGE_AXES_ENABLE), COLLECTION(1, Logical), - USAGE(4, (state->abs_axis_usages[0].UsagePage<<16)|state->abs_axis_usages[0].Usage), - USAGE(4, (state->abs_axis_usages[1].UsagePage<<16)|state->abs_axis_usages[1].Usage), + }; + const BYTE effect_update_middle2[] = + { LOGICAL_MINIMUM(1, 0), LOGICAL_MAXIMUM(1, 1), REPORT_SIZE(1, 1), - REPORT_COUNT(1, 2), + REPORT_COUNT(1, pid_axes), OUTPUT(1, Data|Var|Abs), END_COLLECTION, USAGE(1, PID_USAGE_DIRECTION_ENABLE), REPORT_COUNT(1, 1), OUTPUT(1, Data|Var|Abs), - REPORT_COUNT(1, 5), - OUTPUT(1, Cnst|Var|Abs), /* 5-bit pad */ + REPORT_COUNT(1, (7 - pid_axes) % 8), /* byte pad */ + OUTPUT(1, Cnst|Var|Abs),
USAGE(1, PID_USAGE_DIRECTION), COLLECTION(1, Logical), - USAGE(4, (HID_USAGE_PAGE_ORDINAL<<16)|1), - USAGE(4, (HID_USAGE_PAGE_ORDINAL<<16)|2), + }; + const BYTE effect_update_footer[] = + { UNIT(1, 0x14), /* Eng Rot:Angular Pos */ UNIT_EXPONENT(1, -2), LOGICAL_MINIMUM(1, 0), LOGICAL_MAXIMUM(4, 35900), REPORT_SIZE(1, 16), - REPORT_COUNT(1, 2), + REPORT_COUNT(1, pid_axes), OUTPUT(1, Data|Var|Abs), END_COLLECTION, UNIT_EXPONENT(1, 0), @@ -1033,6 +1060,20 @@ BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT co if (!hid_report_descriptor_append_usage(desc, usages[i])) return FALSE; } + if (!hid_report_descriptor_append(desc, effect_update_middle1, sizeof(effect_update_middle1))) + return FALSE; + + for (i = 0; i < pid_axes; ++i) { + if (!hid_descriptor_add_physical_axis_enable(iface, i)) + return FALSE; + } + if (!hid_report_descriptor_append(desc, effect_update_middle2, sizeof(effect_update_middle2))) + return FALSE; + + for (i = 0; i < pid_axes; ++i) { + if (!hid_descriptor_add_physical_direction(iface, i)) + return FALSE; + } if (!hid_report_descriptor_append(desc, effect_update_footer, sizeof(effect_update_footer))) return FALSE;
@@ -1059,7 +1100,7 @@ BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT co return FALSE; if (envelope && !hid_descriptor_add_set_envelope(iface)) return FALSE; - if (condition && !hid_descriptor_add_set_condition(iface)) + if (condition && !hid_descriptor_add_set_condition(iface, pid_axes)) return FALSE; if (constant_force && !hid_descriptor_add_set_constant_force(iface)) return FALSE; @@ -1076,7 +1117,7 @@ BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT co iface->hid_physical.device_gain_report = device_gain_report; iface->hid_physical.effect_control_report = effect_control_report; iface->hid_physical.effect_update_report = effect_update_report; - iface->hid_physical.num_axes = 2; + iface->hid_physical.num_axes = pid_axes;
effect_state->id = effect_state_report; effect_state->report_len = sizeof(struct pid_effect_state) + 1; diff --git a/dlls/winebus.sys/unix_private.h b/dlls/winebus.sys/unix_private.h index 637e975d068..5d83379dce9 100644 --- a/dlls/winebus.sys/unix_private.h +++ b/dlls/winebus.sys/unix_private.h @@ -255,7 +255,7 @@ extern BOOL hid_device_add_axes(struct unix_device *iface, BYTE count, USAGE usa const USAGE *usages, BOOL rel, LONG min, LONG max);
extern BOOL hid_device_add_haptics(struct unix_device *iface); -extern BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT count); +extern BOOL hid_device_add_physical(struct unix_device *iface, USAGE *usages, USHORT count, USHORT num_axes);
extern BOOL hid_device_set_abs_axis(struct unix_device *iface, ULONG index, LONG value); extern BOOL hid_device_set_rel_axis(struct unix_device *iface, ULONG index, LONG value);
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
Wine will now create PID axes in the virtual descriptor based on the haptic axes found in the SDL joystick.
With the recent SDL hint SDL_JOYSTICK_HAPTIC_AXES, it's possible to limit the exposed FFB axes to just one, even if device presents more. This, in turn fixes force feedback in Richard Burns Rally which includes all ffb axes on effect creation, but then updates this effec with only the steering axis. Changing cAxes and rgdwAxes after effect creation is forbidden and renders force feedback broken.
This issue is not wine-specific as the same thing happens on Windows for all steering wheels which define more than one FFB axis. On windows, this can be worked around with registry hacks which remove DIDFT_FFACTUATOR and DIDOI_FFACTUATOR from second axis dwType and dwFlags.
SDL_HapticNumAxes() was available since SDL 2.0 --- dlls/winebus.sys/bus_sdl.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index 29a0d2cf8d2..f96ab1c3885 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -96,6 +96,7 @@ MAKE_FUNCPTR(SDL_HapticClose); MAKE_FUNCPTR(SDL_HapticDestroyEffect); MAKE_FUNCPTR(SDL_HapticGetEffectStatus); MAKE_FUNCPTR(SDL_HapticNewEffect); +MAKE_FUNCPTR(SDL_HapticNumAxes); MAKE_FUNCPTR(SDL_HapticOpenFromJoystick); MAKE_FUNCPTR(SDL_HapticPause); MAKE_FUNCPTR(SDL_HapticQuery); @@ -226,6 +227,8 @@ static BOOL descriptor_add_haptic(struct sdl_device *impl, BOOL force)
if ((impl->effect_support & EFFECT_SUPPORT_PHYSICAL)) { + LONG num_axes; + /* SDL_HAPTIC_SQUARE doesn't exist */ if (force || (impl->effect_support & SDL_HAPTIC_SINE)) usages[count++] = PID_USAGE_ET_SINE; if (force || (impl->effect_support & SDL_HAPTIC_TRIANGLE)) usages[count++] = PID_USAGE_ET_TRIANGLE; @@ -238,7 +241,13 @@ static BOOL descriptor_add_haptic(struct sdl_device *impl, BOOL force) if (force || (impl->effect_support & SDL_HAPTIC_CONSTANT)) usages[count++] = PID_USAGE_ET_CONSTANT_FORCE; if (force || (impl->effect_support & SDL_HAPTIC_RAMP)) usages[count++] = PID_USAGE_ET_RAMP;
- if (!hid_device_add_physical(&impl->unix_device, usages, count, 2)) + /* Get the number of FFB-enabled axes and hardcode to 2 in case of error. + * (previously hardcoded number of FFB axes) */ + num_axes = pSDL_HapticNumAxes(impl->sdl_haptic); + if (num_axes < 0) + num_axes = 2; + + if (!hid_device_add_physical(&impl->unix_device, usages, count, num_axes)) return FALSE; }
@@ -1122,6 +1131,7 @@ NTSTATUS sdl_bus_init(void *args) LOAD_FUNCPTR(SDL_HapticDestroyEffect); LOAD_FUNCPTR(SDL_HapticGetEffectStatus); LOAD_FUNCPTR(SDL_HapticNewEffect); + LOAD_FUNCPTR(SDL_HapticNumAxes); LOAD_FUNCPTR(SDL_HapticOpenFromJoystick); LOAD_FUNCPTR(SDL_HapticPause); LOAD_FUNCPTR(SDL_HapticQuery);
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
--- dlls/dinput/tests/force_feedback.c | 640 +++++++++++++++++++++++++++++ 1 file changed, 640 insertions(+)
diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index 14bf9575e8f..3ef1e0a760d 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -3659,6 +3659,644 @@ done: return device != NULL; }
+static void test_condition_effect_six_axes( IDirectInputDevice8W *device, HANDLE file ) +{ + struct hid_expect expect_create[] = + { + /* set condition */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 7, + .report_len = 8, + .report_buf = {0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00}, + .todo = TRUE, + }, + /* set condition */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 7, + .report_len = 8, + .report_buf = {0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00}, + .todo = TRUE, + }, + /* update effect */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 3, + .report_len = 8, + .report_buf = {0x03,0x01,0x03,0x40,0x01,0x00,0x00,0x00}, + .todo = TRUE, + }, + }; + static const DWORD expect_axes[2] = + { + DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 4 ) | DIDFT_FFACTUATOR, + DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 5 ) | DIDFT_FFACTUATOR, + }; + static const LONG expect_directions[2] = { + 0, + 0, + }; + static const DICONDITION expect_condition[2] = + { + { + .lOffset = 0, + .lPositiveCoefficient = 0, + .lNegativeCoefficient = 0, + .dwPositiveSaturation = 0, + .dwNegativeSaturation = 0, + .lDeadBand = 0, + }, + { + .lOffset = 0, + .lPositiveCoefficient = 0, + .lNegativeCoefficient = 0, + .dwPositiveSaturation = 0, + .dwNegativeSaturation = 0, + .lDeadBand = 0, + }, + }; + const DIEFFECT expect_desc = + { + .dwSize = sizeof(DIEFFECT_DX6), + .dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS, + .dwDuration = 1000, + .dwGain = 3000, + .dwTriggerRepeatInterval = 0, + .cAxes = 2, + .rgdwAxes = (void *)expect_axes, + .rglDirection = (void *)expect_directions, + .cbTypeSpecificParams = sizeof(DICONDITION), + .lpvTypeSpecificParams = (void *)expect_condition, + .dwStartDelay = 0, + }; + struct check_created_effect_params check_params = {0}; + DIENVELOPE envelope = {.dwSize = sizeof(DIENVELOPE)}; + DICONDITION condition[2] = {{0}}; + IDirectInputEffect *effect; + LONG directions[2] = {0}; + DWORD axes[2] = {0}; + DIEFFECT desc = + { + .dwSize = sizeof(DIEFFECT_DX6), + .dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS, + .cAxes = 2, + .rgdwAxes = axes, + .rglDirection = directions, + .lpEnvelope = &envelope, + .cbTypeSpecificParams = sizeof(DICONDITION), + .lpvTypeSpecificParams = condition, + }; + HRESULT hr; + ULONG ref; + GUID guid; + + set_hid_expect( file, expect_create, sizeof(expect_create) ); + hr = IDirectInputDevice8_CreateEffect( device, &GUID_Spring, &expect_desc, &effect, NULL ); + ok( hr == DI_OK, "CreateEffect returned %#lx\n", hr ); + + check_params.expect_effect = effect; + hr = IDirectInputDevice8_EnumCreatedEffectObjects( device, check_created_effect_objects, &check_params, 0 ); + ok( hr == DI_OK, "EnumCreatedEffectObjects returned %#lx\n", hr ); + ok( check_params.count == 1, "got count %lu, expected 1\n", check_params.count ); + + hr = IDirectInputEffect_GetEffectGuid( effect, &guid ); + ok( hr == DI_OK, "GetEffectGuid returned %#lx\n", hr ); + ok( IsEqualGUID( &guid, &GUID_Spring ), "got guid %s, expected %s\n", debugstr_guid( &guid ), + debugstr_guid( &GUID_Spring ) ); + + hr = IDirectInputEffect_GetParameters( effect, &desc, DIEP_ALLPARAMS); + ok( hr == DI_OK, "GetParameters returned %#lx\n", hr ); + check_member( desc, expect_desc, "%lu", cAxes ); + check_member( desc, expect_desc, "%#lx", rgdwAxes[0] ); + check_member( desc, expect_desc, "%#lx", rgdwAxes[1] ); + check_member( desc, expect_desc, "%ld", rglDirection[0] ); + check_member( desc, expect_desc, "%ld", rglDirection[1] ); + check_member( desc, expect_desc, "%lu", cbTypeSpecificParams ); + + ref = IDirectInputEffect_Release( effect ); + ok( ref == 0, "Release returned %ld\n", ref ); +} + +/* Test if we're getting just one axis in effects */ +static BOOL test_force_feedback_six_axes( void ) +{ +#include "psh_hid_macros.h" + const unsigned char report_descriptor[] = + { + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + USAGE(1, HID_USAGE_GENERIC_JOYSTICK), + COLLECTION(1, Application), + USAGE(1, HID_USAGE_GENERIC_JOYSTICK), + COLLECTION(1, Report), + REPORT_ID(1, 1), + + USAGE(1, HID_USAGE_GENERIC_X), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE(1, HID_USAGE_GENERIC_Y), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE(1, HID_USAGE_GENERIC_Z), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE(1, HID_USAGE_GENERIC_RX), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE(1, HID_USAGE_GENERIC_RY), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE(1, HID_USAGE_GENERIC_RZ), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE_PAGE(1, HID_USAGE_PAGE_BUTTON), + USAGE_MINIMUM(1, 1), + USAGE_MAXIMUM(1, 2), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 1), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 1), + REPORT_SIZE(1, 1), + REPORT_COUNT(1, 2), + INPUT(1, Data|Var|Abs), + REPORT_COUNT(1, 6), + INPUT(1, Cnst|Var|Abs), + END_COLLECTION, + + USAGE_PAGE(1, HID_USAGE_PAGE_PID), + USAGE(1, PID_USAGE_STATE_REPORT), + COLLECTION(1, Report), + REPORT_ID(1, 2), + + USAGE(1, PID_USAGE_DEVICE_PAUSED), + USAGE(1, PID_USAGE_ACTUATORS_ENABLED), + USAGE(1, PID_USAGE_SAFETY_SWITCH), + USAGE(1, PID_USAGE_ACTUATOR_OVERRIDE_SWITCH), + USAGE(1, PID_USAGE_ACTUATOR_POWER), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 1), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 1), + REPORT_SIZE(1, 1), + REPORT_COUNT(1, 5), + INPUT(1, Data|Var|Abs), + REPORT_COUNT(1, 3), + INPUT(1, Cnst|Var|Abs), + + USAGE(1, PID_USAGE_EFFECT_PLAYING), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 1), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 1), + REPORT_SIZE(1, 1), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_EFFECT_BLOCK_INDEX), + LOGICAL_MAXIMUM(1, 0x7f), + LOGICAL_MINIMUM(1, 0x00), + REPORT_SIZE(1, 7), + REPORT_COUNT(1, 1), + INPUT(1, Data|Var|Abs), + END_COLLECTION, + + USAGE_PAGE(1, HID_USAGE_PAGE_PID), + USAGE(1, PID_USAGE_DEVICE_CONTROL_REPORT), + COLLECTION(1, Report), + REPORT_ID(1, 1), + + USAGE(1, PID_USAGE_DEVICE_CONTROL), + COLLECTION(1, Logical), + USAGE(1, PID_USAGE_DC_DEVICE_RESET), + USAGE(1, PID_USAGE_DC_STOP_ALL_EFFECTS), + LOGICAL_MINIMUM(1, 1), + LOGICAL_MAXIMUM(1, 2), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Ary|Abs), + END_COLLECTION, + END_COLLECTION, + + USAGE(1, PID_USAGE_EFFECT_OPERATION_REPORT), + COLLECTION(1, Report), + REPORT_ID(1, 2), + + USAGE(1, PID_USAGE_EFFECT_BLOCK_INDEX), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_EFFECT_OPERATION), + COLLECTION(1, NamedArray), + USAGE(1, PID_USAGE_OP_EFFECT_START), + USAGE(1, PID_USAGE_OP_EFFECT_START_SOLO), + USAGE(1, PID_USAGE_OP_EFFECT_STOP), + LOGICAL_MINIMUM(1, 1), + LOGICAL_MAXIMUM(1, 3), + PHYSICAL_MINIMUM(1, 1), + PHYSICAL_MAXIMUM(1, 3), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Ary|Abs), + END_COLLECTION, + + USAGE(1, PID_USAGE_LOOP_COUNT), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + END_COLLECTION, + + USAGE(1, PID_USAGE_SET_EFFECT_REPORT), + COLLECTION(1, Report), + REPORT_ID(1, 3), + + USAGE(1, PID_USAGE_EFFECT_BLOCK_INDEX), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0x7f), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_EFFECT_TYPE), + COLLECTION(1, NamedArray), + USAGE(1, PID_USAGE_ET_SQUARE), + USAGE(1, PID_USAGE_ET_SINE), + USAGE(1, PID_USAGE_ET_SPRING), + USAGE(1, PID_USAGE_ET_CONSTANT_FORCE), + LOGICAL_MINIMUM(1, 1), + LOGICAL_MAXIMUM(1, 4), + PHYSICAL_MINIMUM(1, 1), + PHYSICAL_MAXIMUM(1, 4), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Ary|Abs), + END_COLLECTION, + + USAGE(1, PID_USAGE_AXES_ENABLE), + COLLECTION(1, Logical), + USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_X), + USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_Y), + USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_Z), + USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_RX), + USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_RY), + USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_RZ), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 1), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 1), + REPORT_SIZE(1, 1), + REPORT_COUNT(1, 6), + OUTPUT(1, Data|Var|Abs), + END_COLLECTION, + USAGE(1, PID_USAGE_DIRECTION_ENABLE), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + REPORT_COUNT(1, 1), /* byte pading */ + OUTPUT(1, Cnst|Var|Abs), + + USAGE(1, PID_USAGE_DURATION), + USAGE(1, PID_USAGE_START_DELAY), + UNIT(2, 0x1003), /* Eng Lin:Time */ + UNIT_EXPONENT(1, -3), /* 10^-3 */ + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(2, 0x7fff), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(2, 0x7fff), + REPORT_SIZE(1, 16), + REPORT_COUNT(1, 2), + OUTPUT(1, Data|Var|Abs), + UNIT(1, 0), + UNIT_EXPONENT(1, 0), + + USAGE(1, PID_USAGE_TRIGGER_BUTTON), + LOGICAL_MINIMUM(1, 1), + LOGICAL_MAXIMUM(1, 0x08), + PHYSICAL_MINIMUM(1, 1), + PHYSICAL_MAXIMUM(1, 0x08), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_DIRECTION), + COLLECTION(1, Logical), + USAGE(4, (HID_USAGE_PAGE_ORDINAL << 16)|1), + USAGE(4, (HID_USAGE_PAGE_ORDINAL << 16)|2), + USAGE(4, (HID_USAGE_PAGE_ORDINAL << 16)|3), + USAGE(4, (HID_USAGE_PAGE_ORDINAL << 16)|4), + USAGE(4, (HID_USAGE_PAGE_ORDINAL << 16)|5), + USAGE(4, (HID_USAGE_PAGE_ORDINAL << 16)|6), + UNIT(1, 0x14), /* Eng Rot:Angular Pos */ + UNIT_EXPONENT(1, 0), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0xff), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(4, 0x00008ca0), + UNIT(1, 0), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 6), + OUTPUT(1, Data|Var|Abs), + UNIT_EXPONENT(1, 0), + UNIT(1, 0), + END_COLLECTION, + END_COLLECTION, + + USAGE(1, PID_USAGE_SET_PERIODIC_REPORT), + COLLECTION(1, Logical), + REPORT_ID(1, 5), + + USAGE(1, PID_USAGE_MAGNITUDE), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(2, 0x00ff), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(2, 0x2710), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + END_COLLECTION, + + USAGE(1, PID_USAGE_SET_ENVELOPE_REPORT), + COLLECTION(1, Logical), + REPORT_ID(1, 6), + + USAGE(1, PID_USAGE_ATTACK_LEVEL), + USAGE(1, PID_USAGE_FADE_LEVEL), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(2, 0x00ff), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(2, 0x2710), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 2), + OUTPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_ATTACK_TIME), + USAGE(1, PID_USAGE_FADE_TIME), + UNIT(2, 0x1003), /* Eng Lin:Time */ + UNIT_EXPONENT(1, -3), /* 10^-3 */ + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(2, 0x7fff), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(2, 0x7fff), + REPORT_SIZE(1, 16), + REPORT_COUNT(1, 2), + OUTPUT(1, Data|Var|Abs), + PHYSICAL_MAXIMUM(1, 0), + UNIT_EXPONENT(1, 0), + UNIT(1, 0), + END_COLLECTION, + + + USAGE(1, PID_USAGE_SET_CONDITION_REPORT), + COLLECTION(1, Logical), + REPORT_ID(1, 7), + + USAGE(1, PID_USAGE_PARAMETER_BLOCK_OFFSET), + LOGICAL_MINIMUM(1, 0x00), + LOGICAL_MAXIMUM(1, 0x05), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_CP_OFFSET), + USAGE(1, PID_USAGE_POSITIVE_COEFFICIENT), + USAGE(1, PID_USAGE_NEGATIVE_COEFFICIENT), + LOGICAL_MINIMUM(1, 0x80), + LOGICAL_MAXIMUM(1, 0x7f), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 3), + OUTPUT(1, Data|Var|Abs), + + USAGE(1, PID_USAGE_POSITIVE_SATURATION), + USAGE(1, PID_USAGE_NEGATIVE_SATURATION), + USAGE(1, PID_USAGE_DEAD_BAND), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 0xff), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 3), + OUTPUT(1, Data|Var|Abs), + END_COLLECTION, + + + USAGE(1, PID_USAGE_DEVICE_GAIN_REPORT), + COLLECTION(1, Logical), + REPORT_ID(1, 8), + + USAGE(1, PID_USAGE_DEVICE_GAIN), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(2, 0x00ff), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(2, 0x2710), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + END_COLLECTION, + + USAGE(1, PID_USAGE_SET_CONSTANT_FORCE_REPORT), + COLLECTION(1, Logical), + REPORT_ID(1, 9), + + USAGE(1, PID_USAGE_MAGNITUDE), + LOGICAL_MINIMUM(2, 0xd8f0), + LOGICAL_MAXIMUM(2, 0x2710), + PHYSICAL_MINIMUM(2, 0xd8f0), + PHYSICAL_MAXIMUM(2, 0x2710), + REPORT_SIZE(1, 16), + REPORT_COUNT(1, 1), + OUTPUT(1, Data|Var|Abs), + END_COLLECTION, + + END_COLLECTION, + }; + C_ASSERT(sizeof(report_descriptor) < MAX_HID_DESCRIPTOR_LEN); +#include "pop_hid_macros.h" + + struct hid_device_desc desc = + { + .use_report_id = TRUE, + .caps = + { + .InputReportByteLength = 5, + .OutputReportByteLength = 11, + }, + .attributes = default_attributes, + }; + const DIDEVCAPS expect_caps = + { + .dwSize = sizeof(DIDEVCAPS), + .dwFlags = DIDC_FORCEFEEDBACK | DIDC_ATTACHED | DIDC_EMULATED | DIDC_STARTDELAY | + DIDC_FFFADE | DIDC_FFATTACK | DIDC_DEADBAND | DIDC_SATURATION, + .dwDevType = DIDEVTYPE_HID | (DI8DEVTYPEJOYSTICK_LIMITED << 8) | DI8DEVTYPE_JOYSTICK, + .dwAxes = 6, + .dwButtons = 2, + .dwFFSamplePeriod = 1000000, + .dwFFMinTimeResolution = 1000000, + .dwHardwareRevision = 1, + .dwFFDriverVersion = 1, + }; + const DIDEVICEINSTANCEW expect_devinst = + { + .dwSize = sizeof(DIDEVICEINSTANCEW), + .guidInstance = expect_guid_product, + .guidProduct = expect_guid_product, + .dwDevType = DIDEVTYPE_HID | (DI8DEVTYPEJOYSTICK_LIMITED << 8) | DI8DEVTYPE_JOYSTICK, + .tszInstanceName = L"Wine Test", + .tszProductName = L"Wine Test", + .guidFFDriver = IID_IDirectInputPIDDriver, + .wUsagePage = HID_USAGE_PAGE_GENERIC, + .wUsage = HID_USAGE_GENERIC_JOYSTICK, + }; + DIPROPDWORD prop_dword = + { + .diph = + { + .dwSize = sizeof(DIPROPDWORD), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwHow = DIPH_DEVICE, + }, + }; + DIPROPGUIDANDPATH prop_guid_path = + { + .diph = + { + .dwSize = sizeof(DIPROPGUIDANDPATH), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwHow = DIPH_DEVICE, + }, + }; + DIDEVICEINSTANCEW devinst = {.dwSize = sizeof(DIDEVICEINSTANCEW)}; + IDirectInputDevice8W *device = NULL; + DIDEVCAPS caps = {0}; + DWORD version = 0x800; + ULONG ref; + HANDLE file; + HRESULT hr; + HWND hwnd; + + winetest_push_context( "%#lx", version ); + cleanup_registry_keys(); + + desc.report_descriptor_len = sizeof(report_descriptor); + memcpy( desc.report_descriptor_buf, report_descriptor, sizeof(report_descriptor) ); + fill_context( desc.context, ARRAY_SIZE(desc.context) ); + + if (!hid_device_start( &desc, 1 )) goto done; + if (FAILED(hr = dinput_test_create_device( version, &devinst, &device ))) goto done; + + check_dinput_devices( version, &devinst ); + + hr = IDirectInputDevice8_GetDeviceInfo( device, &devinst ); + ok( hr == DI_OK, "GetDeviceInfo returned %#lx\n", hr ); + check_member( devinst, expect_devinst, "%lu", dwSize ); + check_member_guid( devinst, expect_devinst, guidProduct ); + check_member( devinst, expect_devinst, "%#lx", dwDevType ); + check_member_wstr( devinst, expect_devinst, tszInstanceName ); + check_member_wstr( devinst, expect_devinst, tszProductName ); + check_member_guid( devinst, expect_devinst, guidFFDriver ); + check_member( devinst, expect_devinst, "%04x", wUsagePage ); + check_member( devinst, expect_devinst, "%04x", wUsage ); + + caps.dwSize = sizeof(DIDEVCAPS); + hr = IDirectInputDevice8_GetCapabilities( device, &caps ); + ok( hr == DI_OK, "GetCapabilities returned %#lx\n", hr ); + check_member( caps, expect_caps, "%lu", dwSize ); + check_member( caps, expect_caps, "%#lx", dwFlags ); + check_member( caps, expect_caps, "%#lx", dwDevType ); + check_member( caps, expect_caps, "%lu", dwAxes ); + check_member( caps, expect_caps, "%lu", dwButtons ); + check_member( caps, expect_caps, "%lu", dwPOVs ); + check_member( caps, expect_caps, "%lu", dwFFSamplePeriod ); + check_member( caps, expect_caps, "%lu", dwFFMinTimeResolution ); + check_member( caps, expect_caps, "%lu", dwFirmwareRevision ); + check_member( caps, expect_caps, "%lu", dwHardwareRevision ); + check_member( caps, expect_caps, "%lu", dwFFDriverVersion ); + + prop_dword.dwData = 0xdeadbeef; + hr = IDirectInputDevice8_GetProperty( device, DIPROP_FFGAIN, &prop_dword.diph ); + ok( hr == DI_OK, "GetProperty DIPROP_FFGAIN returned %#lx\n", hr ); + ok( prop_dword.dwData == 10000, "got %lu expected %u\n", prop_dword.dwData, 10000 ); + + hr = IDirectInputDevice8_GetProperty( device, DIPROP_FFLOAD, &prop_dword.diph ); + ok( hr == DIERR_NOTEXCLUSIVEACQUIRED, "GetProperty DIPROP_FFLOAD returned %#lx\n", hr ); + + hr = IDirectInputDevice8_SetDataFormat( device, &c_dfDIJoystick2 ); + ok( hr == DI_OK, "SetDataFormat returned: %#lx\n", hr ); + + hr = IDirectInputDevice8_GetProperty( device, DIPROP_GUIDANDPATH, &prop_guid_path.diph ); + ok( hr == DI_OK, "GetProperty DIPROP_GUIDANDPATH returned %#lx\n", hr ); + + file = CreateFileW( prop_guid_path.wszPath, FILE_READ_ACCESS | FILE_WRITE_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL ); + ok( file != INVALID_HANDLE_VALUE, "got error %lu\n", GetLastError() ); + + hwnd = create_foreground_window( FALSE ); + + hr = IDirectInputDevice8_Acquire( device ); + ok( hr == DI_OK, "Acquire returned: %#lx\n", hr ); + + test_condition_effect_six_axes( device, file ); + + hr = IDirectInputDevice8_Unacquire( device ); + ok( hr == DI_OK, "Unacquire returned: %#lx\n", hr ); + + ref = IDirectInputDevice8_Release( device ); + ok( ref == 0, "Release returned %ld\n", ref ); + + DestroyWindow( hwnd ); + CloseHandle( file ); + +done: + hid_device_stop( &desc, 1 ); + cleanup_registry_keys(); + winetest_pop_context(); + return device != NULL; +} + static void test_device_managed_effect(void) { #include "psh_hid_macros.h" @@ -7226,6 +7864,8 @@ START_TEST( force_feedback ) test_force_feedback_joystick( 0x700 ); test_device_managed_effect(); test_windows_gaming_input(); + + test_force_feedback_six_axes(); }
done:
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
--- dlls/dinput/tests/driver_bus.c | 4 +- dlls/dinput/tests/force_feedback.c | 238 ++++++++++++++--------------- 2 files changed, 121 insertions(+), 121 deletions(-)
diff --git a/dlls/dinput/tests/driver_bus.c b/dlls/dinput/tests/driver_bus.c index cf42a1bcf6e..26df9903a4b 100644 --- a/dlls/dinput/tests/driver_bus.c +++ b/dlls/dinput/tests/driver_bus.c @@ -1160,8 +1160,8 @@ static NTSTATUS pdo_internal_ioctl( DEVICE_OBJECT *device, IRP *irp ) expect_queue_next( &impl->expect_queue, code, packet, &index, &expect, FALSE, context, sizeof(context) ); winetest_push_context( "%s expect[%ld]", context, index ); ok( code == expect.code, "got %#lx, expected %#lx\n", code, expect.code ); - ok( packet->reportId == expect.report_id, "got id %u\n", packet->reportId ); - ok( packet->reportBufferLen == expect.report_len, "got len %lu\n", packet->reportBufferLen ); + ok( packet->reportId == expect.report_id, "got id: %u\n, expected: %u", packet->reportId, expect.report_id ); + ok( packet->reportBufferLen == expect.report_len, "got len: %lu\n, expected: %lu", packet->reportBufferLen, expect.report_len ); winetest_pop_context();
irp->IoStatus.Information = expect.ret_length ? expect.ret_length : expect.report_len; diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index 3ef1e0a760d..634bd975582 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -3659,124 +3659,124 @@ done: return device != NULL; }
-static void test_condition_effect_six_axes( IDirectInputDevice8W *device, HANDLE file ) -{ - struct hid_expect expect_create[] = - { - /* set condition */ - { - .code = IOCTL_HID_WRITE_REPORT, - .report_id = 7, - .report_len = 8, - .report_buf = {0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00}, - .todo = TRUE, - }, - /* set condition */ - { - .code = IOCTL_HID_WRITE_REPORT, - .report_id = 7, - .report_len = 8, - .report_buf = {0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00}, - .todo = TRUE, - }, - /* update effect */ - { - .code = IOCTL_HID_WRITE_REPORT, - .report_id = 3, - .report_len = 8, - .report_buf = {0x03,0x01,0x03,0x40,0x01,0x00,0x00,0x00}, - .todo = TRUE, - }, - }; - static const DWORD expect_axes[2] = - { - DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 4 ) | DIDFT_FFACTUATOR, - DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 5 ) | DIDFT_FFACTUATOR, - }; - static const LONG expect_directions[2] = { - 0, - 0, - }; - static const DICONDITION expect_condition[2] = - { - { - .lOffset = 0, - .lPositiveCoefficient = 0, - .lNegativeCoefficient = 0, - .dwPositiveSaturation = 0, - .dwNegativeSaturation = 0, - .lDeadBand = 0, - }, - { - .lOffset = 0, - .lPositiveCoefficient = 0, - .lNegativeCoefficient = 0, - .dwPositiveSaturation = 0, - .dwNegativeSaturation = 0, - .lDeadBand = 0, - }, - }; - const DIEFFECT expect_desc = - { - .dwSize = sizeof(DIEFFECT_DX6), - .dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS, - .dwDuration = 1000, - .dwGain = 3000, - .dwTriggerRepeatInterval = 0, - .cAxes = 2, - .rgdwAxes = (void *)expect_axes, - .rglDirection = (void *)expect_directions, - .cbTypeSpecificParams = sizeof(DICONDITION), - .lpvTypeSpecificParams = (void *)expect_condition, - .dwStartDelay = 0, - }; - struct check_created_effect_params check_params = {0}; - DIENVELOPE envelope = {.dwSize = sizeof(DIENVELOPE)}; - DICONDITION condition[2] = {{0}}; - IDirectInputEffect *effect; - LONG directions[2] = {0}; - DWORD axes[2] = {0}; - DIEFFECT desc = - { - .dwSize = sizeof(DIEFFECT_DX6), - .dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS, - .cAxes = 2, - .rgdwAxes = axes, - .rglDirection = directions, - .lpEnvelope = &envelope, - .cbTypeSpecificParams = sizeof(DICONDITION), - .lpvTypeSpecificParams = condition, - }; - HRESULT hr; - ULONG ref; - GUID guid; - - set_hid_expect( file, expect_create, sizeof(expect_create) ); - hr = IDirectInputDevice8_CreateEffect( device, &GUID_Spring, &expect_desc, &effect, NULL ); - ok( hr == DI_OK, "CreateEffect returned %#lx\n", hr ); - - check_params.expect_effect = effect; - hr = IDirectInputDevice8_EnumCreatedEffectObjects( device, check_created_effect_objects, &check_params, 0 ); - ok( hr == DI_OK, "EnumCreatedEffectObjects returned %#lx\n", hr ); - ok( check_params.count == 1, "got count %lu, expected 1\n", check_params.count ); - - hr = IDirectInputEffect_GetEffectGuid( effect, &guid ); - ok( hr == DI_OK, "GetEffectGuid returned %#lx\n", hr ); - ok( IsEqualGUID( &guid, &GUID_Spring ), "got guid %s, expected %s\n", debugstr_guid( &guid ), - debugstr_guid( &GUID_Spring ) ); - - hr = IDirectInputEffect_GetParameters( effect, &desc, DIEP_ALLPARAMS); - ok( hr == DI_OK, "GetParameters returned %#lx\n", hr ); - check_member( desc, expect_desc, "%lu", cAxes ); - check_member( desc, expect_desc, "%#lx", rgdwAxes[0] ); - check_member( desc, expect_desc, "%#lx", rgdwAxes[1] ); - check_member( desc, expect_desc, "%ld", rglDirection[0] ); - check_member( desc, expect_desc, "%ld", rglDirection[1] ); - check_member( desc, expect_desc, "%lu", cbTypeSpecificParams ); - - ref = IDirectInputEffect_Release( effect ); - ok( ref == 0, "Release returned %ld\n", ref ); -} +// static void test_condition_effect_six_axes( IDirectInputDevice8W *device, HANDLE file ) +// { +// struct hid_expect expect_create[] = +// { +// /* set condition */ +// { +// .code = IOCTL_HID_WRITE_REPORT, +// .report_id = 7, +// .report_len = 8, +// .report_buf = {0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00}, +// .todo = TRUE, +// }, +// /* set condition */ +// { +// .code = IOCTL_HID_WRITE_REPORT, +// .report_id = 7, +// .report_len = 8, +// .report_buf = {0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00}, +// .todo = TRUE, +// }, +// /* update effect */ +// { +// .code = IOCTL_HID_WRITE_REPORT, +// .report_id = 3, +// .report_len = 8, +// .report_buf = {0x03,0x01,0x03,0x40,0x01,0x00,0x00,0x00}, +// .todo = TRUE, +// }, +// }; +// static const DWORD expect_axes[2] = +// { +// DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 4 ) | DIDFT_FFACTUATOR, +// DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 5 ) | DIDFT_FFACTUATOR, +// }; +// static const LONG expect_directions[2] = { +// 0, +// 0, +// }; +// static const DICONDITION expect_condition[2] = +// { +// { +// .lOffset = 0, +// .lPositiveCoefficient = 0, +// .lNegativeCoefficient = 0, +// .dwPositiveSaturation = 0, +// .dwNegativeSaturation = 0, +// .lDeadBand = 0, +// }, +// { +// .lOffset = 0, +// .lPositiveCoefficient = 0, +// .lNegativeCoefficient = 0, +// .dwPositiveSaturation = 0, +// .dwNegativeSaturation = 0, +// .lDeadBand = 0, +// }, +// }; +// const DIEFFECT expect_desc = +// { +// .dwSize = sizeof(DIEFFECT_DX6), +// .dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS, +// .dwDuration = 1000, +// .dwGain = 3000, +// .dwTriggerRepeatInterval = 0, +// .cAxes = 2, +// .rgdwAxes = (void *)expect_axes, +// .rglDirection = (void *)expect_directions, +// .cbTypeSpecificParams = sizeof(DICONDITION), +// .lpvTypeSpecificParams = (void *)expect_condition, +// .dwStartDelay = 0, +// }; +// struct check_created_effect_params check_params = {0}; +// DIENVELOPE envelope = {.dwSize = sizeof(DIENVELOPE)}; +// DICONDITION condition[2] = {{0}}; +// IDirectInputEffect *effect; +// LONG directions[2] = {0}; +// DWORD axes[2] = {0}; +// DIEFFECT desc = +// { +// .dwSize = sizeof(DIEFFECT_DX6), +// .dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS, +// .cAxes = 2, +// .rgdwAxes = axes, +// .rglDirection = directions, +// .lpEnvelope = &envelope, +// .cbTypeSpecificParams = sizeof(DICONDITION), +// .lpvTypeSpecificParams = condition, +// }; +// HRESULT hr; +// ULONG ref; +// GUID guid; + +// set_hid_expect( file, expect_create, sizeof(expect_create) ); +// hr = IDirectInputDevice8_CreateEffect( device, &GUID_Spring, &expect_desc, &effect, NULL ); +// ok( hr == DI_OK, "CreateEffect returned %#lx\n", hr ); + +// check_params.expect_effect = effect; +// hr = IDirectInputDevice8_EnumCreatedEffectObjects( device, check_created_effect_objects, &check_params, 0 ); +// ok( hr == DI_OK, "EnumCreatedEffectObjects returned %#lx\n", hr ); +// ok( check_params.count == 1, "got count %lu, expected 1\n", check_params.count ); + +// hr = IDirectInputEffect_GetEffectGuid( effect, &guid ); +// ok( hr == DI_OK, "GetEffectGuid returned %#lx\n", hr ); +// ok( IsEqualGUID( &guid, &GUID_Spring ), "got guid %s, expected %s\n", debugstr_guid( &guid ), +// debugstr_guid( &GUID_Spring ) ); + +// hr = IDirectInputEffect_GetParameters( effect, &desc, DIEP_ALLPARAMS); +// ok( hr == DI_OK, "GetParameters returned %#lx\n", hr ); +// check_member( desc, expect_desc, "%lu", cAxes ); +// check_member( desc, expect_desc, "%#lx", rgdwAxes[0] ); +// check_member( desc, expect_desc, "%#lx", rgdwAxes[1] ); +// check_member( desc, expect_desc, "%ld", rglDirection[0] ); +// check_member( desc, expect_desc, "%ld", rglDirection[1] ); +// check_member( desc, expect_desc, "%lu", cbTypeSpecificParams ); + +// ref = IDirectInputEffect_Release( effect ); +// ok( ref == 0, "Release returned %ld\n", ref ); +// }
/* Test if we're getting just one axis in effects */ static BOOL test_force_feedback_six_axes( void ) @@ -4279,7 +4279,7 @@ static BOOL test_force_feedback_six_axes( void ) hr = IDirectInputDevice8_Acquire( device ); ok( hr == DI_OK, "Acquire returned: %#lx\n", hr );
- test_condition_effect_six_axes( device, file ); + // test_condition_effect_six_axes( device, file );
hr = IDirectInputDevice8_Unacquire( device ); ok( hr == DI_OK, "Unacquire returned: %#lx\n", hr );