3056 lines
127 KiB
C
3056 lines
127 KiB
C
/* DirectInput HID Joystick device
|
|
*
|
|
* Copyright 2021 Rémi Bernon for CodeWeavers
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "ntstatus.h"
|
|
#define WIN32_NO_STATUS
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winternl.h"
|
|
#include "winuser.h"
|
|
#include "winerror.h"
|
|
#include "winreg.h"
|
|
|
|
#include "ddk/hidsdi.h"
|
|
#include "setupapi.h"
|
|
#include "devguid.h"
|
|
#include "dinput.h"
|
|
#include "setupapi.h"
|
|
|
|
#include "dinput_private.h"
|
|
#include "device_private.h"
|
|
|
|
#include "initguid.h"
|
|
#include "devpkey.h"
|
|
|
|
#include "wine/debug.h"
|
|
#include "wine/hid.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(dinput);
|
|
|
|
DEFINE_GUID( GUID_DEVINTERFACE_WINEXINPUT,0x6c53d5fd,0x6480,0x440f,0xb6,0x18,0x47,0x67,0x50,0xc5,0xe1,0xa6 );
|
|
DEFINE_GUID( hid_joystick_guid, 0x9e573edb, 0x7734, 0x11d2, 0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf7 );
|
|
DEFINE_DEVPROPKEY( DEVPROPKEY_HID_HANDLE, 0xbc62e415, 0xf4fe, 0x405c, 0x8e, 0xda, 0x63, 0x6f, 0xb5, 0x9f, 0x08, 0x98, 2 );
|
|
|
|
struct pid_control_report
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
ULONG control_coll;
|
|
};
|
|
|
|
struct pid_effect_update
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
ULONG type_coll;
|
|
ULONG axes_coll;
|
|
ULONG axis_count;
|
|
ULONG direction_coll;
|
|
ULONG direction_count;
|
|
struct hid_value_caps *axis_caps[6];
|
|
struct hid_value_caps *direction_caps[6];
|
|
struct hid_value_caps *duration_caps;
|
|
struct hid_value_caps *gain_caps;
|
|
struct hid_value_caps *sample_period_caps;
|
|
struct hid_value_caps *start_delay_caps;
|
|
struct hid_value_caps *trigger_button_caps;
|
|
struct hid_value_caps *trigger_repeat_interval_caps;
|
|
};
|
|
|
|
struct pid_set_periodic
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *magnitude_caps;
|
|
struct hid_value_caps *period_caps;
|
|
struct hid_value_caps *phase_caps;
|
|
struct hid_value_caps *offset_caps;
|
|
};
|
|
|
|
struct pid_set_envelope
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *attack_level_caps;
|
|
struct hid_value_caps *attack_time_caps;
|
|
struct hid_value_caps *fade_level_caps;
|
|
struct hid_value_caps *fade_time_caps;
|
|
};
|
|
|
|
struct pid_set_condition
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *center_point_offset_caps;
|
|
struct hid_value_caps *positive_coefficient_caps;
|
|
struct hid_value_caps *negative_coefficient_caps;
|
|
struct hid_value_caps *positive_saturation_caps;
|
|
struct hid_value_caps *negative_saturation_caps;
|
|
struct hid_value_caps *dead_band_caps;
|
|
};
|
|
|
|
struct pid_set_constant_force
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *magnitude_caps;
|
|
};
|
|
|
|
struct pid_set_ramp_force
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *start_caps;
|
|
struct hid_value_caps *end_caps;
|
|
};
|
|
|
|
struct pid_device_gain
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *device_gain_caps;
|
|
};
|
|
|
|
struct pid_device_pool
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
struct hid_value_caps *device_managed_caps;
|
|
};
|
|
|
|
struct pid_block_free
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
};
|
|
|
|
struct pid_block_load
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
ULONG status_coll;
|
|
};
|
|
|
|
struct pid_new_effect
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
ULONG type_coll;
|
|
};
|
|
|
|
struct hid_joystick
|
|
{
|
|
struct dinput_device base;
|
|
LONG internal_ref;
|
|
|
|
HANDLE device;
|
|
OVERLAPPED read_ovl;
|
|
PHIDP_PREPARSED_DATA preparsed;
|
|
|
|
WCHAR device_path[MAX_PATH];
|
|
HIDD_ATTRIBUTES attrs;
|
|
HIDP_CAPS caps;
|
|
|
|
char *input_report_buf;
|
|
char *output_report_buf;
|
|
char *feature_report_buf;
|
|
USAGE_AND_PAGE *usages_buf;
|
|
ULONG usages_count;
|
|
|
|
BYTE effect_inuse[255];
|
|
struct list effect_list;
|
|
struct pid_control_report pid_device_control;
|
|
struct pid_control_report pid_effect_control;
|
|
struct pid_effect_update pid_effect_update;
|
|
struct pid_set_periodic pid_set_periodic;
|
|
struct pid_set_envelope pid_set_envelope;
|
|
struct pid_set_condition pid_set_condition;
|
|
struct pid_set_constant_force pid_set_constant_force;
|
|
struct pid_set_ramp_force pid_set_ramp_force;
|
|
struct pid_device_gain pid_device_gain;
|
|
struct pid_device_pool pid_device_pool;
|
|
struct pid_block_free pid_block_free;
|
|
struct pid_block_load pid_block_load;
|
|
struct pid_new_effect pid_new_effect;
|
|
};
|
|
|
|
static inline struct hid_joystick *impl_from_IDirectInputDevice8W( IDirectInputDevice8W *iface )
|
|
{
|
|
return CONTAINING_RECORD( CONTAINING_RECORD( iface, struct dinput_device, IDirectInputDevice8W_iface ),
|
|
struct hid_joystick, base );
|
|
}
|
|
|
|
struct hid_joystick_effect
|
|
{
|
|
IDirectInputEffect IDirectInputEffect_iface;
|
|
LONG ref;
|
|
USAGE type;
|
|
ULONG index;
|
|
|
|
struct list entry;
|
|
struct hid_joystick *joystick;
|
|
|
|
DWORD axes[6];
|
|
LONG directions[6];
|
|
DICONSTANTFORCE constant_force;
|
|
DIRAMPFORCE ramp_force;
|
|
DICONDITION condition[6];
|
|
DIENVELOPE envelope;
|
|
DIPERIODIC periodic;
|
|
DIEFFECT params;
|
|
DWORD modified;
|
|
DWORD flags;
|
|
|
|
char *effect_control_buf;
|
|
char *effect_update_buf;
|
|
char *type_specific_buf;
|
|
char *set_envelope_buf;
|
|
};
|
|
|
|
static inline struct hid_joystick_effect *impl_from_IDirectInputEffect( IDirectInputEffect *iface )
|
|
{
|
|
return CONTAINING_RECORD( iface, struct hid_joystick_effect, IDirectInputEffect_iface );
|
|
}
|
|
|
|
static const GUID *object_usage_to_guid( USAGE usage_page, USAGE usage )
|
|
{
|
|
switch (usage_page)
|
|
{
|
|
case HID_USAGE_PAGE_BUTTON: return &GUID_Button;
|
|
case HID_USAGE_PAGE_SIMULATION:
|
|
switch (usage)
|
|
{
|
|
case HID_USAGE_SIMULATION_STEERING: return &GUID_XAxis;
|
|
case HID_USAGE_SIMULATION_ACCELERATOR: return &GUID_YAxis;
|
|
case HID_USAGE_SIMULATION_BRAKE: return &GUID_RzAxis;
|
|
case HID_USAGE_SIMULATION_RUDDER: return &GUID_RzAxis;
|
|
case HID_USAGE_SIMULATION_THROTTLE: return &GUID_Slider;
|
|
}
|
|
break;
|
|
case HID_USAGE_PAGE_GENERIC:
|
|
switch (usage)
|
|
{
|
|
case HID_USAGE_GENERIC_X: return &GUID_XAxis;
|
|
case HID_USAGE_GENERIC_Y: return &GUID_YAxis;
|
|
case HID_USAGE_GENERIC_Z: return &GUID_ZAxis;
|
|
case HID_USAGE_GENERIC_WHEEL: return &GUID_ZAxis;
|
|
case HID_USAGE_GENERIC_RX: return &GUID_RxAxis;
|
|
case HID_USAGE_GENERIC_RY: return &GUID_RyAxis;
|
|
case HID_USAGE_GENERIC_RZ: return &GUID_RzAxis;
|
|
case HID_USAGE_GENERIC_SLIDER: return &GUID_Slider;
|
|
case HID_USAGE_GENERIC_HATSWITCH: return &GUID_POV;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return &GUID_Unknown;
|
|
}
|
|
|
|
static inline USAGE effect_guid_to_usage( const GUID *guid )
|
|
{
|
|
if (IsEqualGUID( guid, &GUID_CustomForce )) return PID_USAGE_ET_CUSTOM_FORCE_DATA;
|
|
if (IsEqualGUID( guid, &GUID_ConstantForce )) return PID_USAGE_ET_CONSTANT_FORCE;
|
|
if (IsEqualGUID( guid, &GUID_RampForce )) return PID_USAGE_ET_RAMP;
|
|
if (IsEqualGUID( guid, &GUID_Square )) return PID_USAGE_ET_SQUARE;
|
|
if (IsEqualGUID( guid, &GUID_Sine )) return PID_USAGE_ET_SINE;
|
|
if (IsEqualGUID( guid, &GUID_Triangle )) return PID_USAGE_ET_TRIANGLE;
|
|
if (IsEqualGUID( guid, &GUID_SawtoothUp )) return PID_USAGE_ET_SAWTOOTH_UP;
|
|
if (IsEqualGUID( guid, &GUID_SawtoothDown )) return PID_USAGE_ET_SAWTOOTH_DOWN;
|
|
if (IsEqualGUID( guid, &GUID_Spring )) return PID_USAGE_ET_SPRING;
|
|
if (IsEqualGUID( guid, &GUID_Damper )) return PID_USAGE_ET_DAMPER;
|
|
if (IsEqualGUID( guid, &GUID_Inertia )) return PID_USAGE_ET_INERTIA;
|
|
if (IsEqualGUID( guid, &GUID_Friction )) return PID_USAGE_ET_FRICTION;
|
|
return 0;
|
|
}
|
|
|
|
static inline const GUID *effect_usage_to_guid( USAGE usage )
|
|
{
|
|
switch (usage)
|
|
{
|
|
case PID_USAGE_ET_CUSTOM_FORCE_DATA: return &GUID_CustomForce;
|
|
case PID_USAGE_ET_CONSTANT_FORCE: return &GUID_ConstantForce;
|
|
case PID_USAGE_ET_RAMP: return &GUID_RampForce;
|
|
case PID_USAGE_ET_SQUARE: return &GUID_Square;
|
|
case PID_USAGE_ET_SINE: return &GUID_Sine;
|
|
case PID_USAGE_ET_TRIANGLE: return &GUID_Triangle;
|
|
case PID_USAGE_ET_SAWTOOTH_UP: return &GUID_SawtoothUp;
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN: return &GUID_SawtoothDown;
|
|
case PID_USAGE_ET_SPRING: return &GUID_Spring;
|
|
case PID_USAGE_ET_DAMPER: return &GUID_Damper;
|
|
case PID_USAGE_ET_INERTIA: return &GUID_Inertia;
|
|
case PID_USAGE_ET_FRICTION: return &GUID_Friction;
|
|
}
|
|
return &GUID_Unknown;
|
|
}
|
|
|
|
static const WCHAR *effect_guid_to_string( const GUID *guid )
|
|
{
|
|
if (IsEqualGUID( guid, &GUID_CustomForce )) return L"GUID_CustomForce";
|
|
if (IsEqualGUID( guid, &GUID_ConstantForce )) return L"GUID_ConstantForce";
|
|
if (IsEqualGUID( guid, &GUID_RampForce )) return L"GUID_RampForce";
|
|
if (IsEqualGUID( guid, &GUID_Square )) return L"GUID_Square";
|
|
if (IsEqualGUID( guid, &GUID_Sine )) return L"GUID_Sine";
|
|
if (IsEqualGUID( guid, &GUID_Triangle )) return L"GUID_Triangle";
|
|
if (IsEqualGUID( guid, &GUID_SawtoothUp )) return L"GUID_SawtoothUp";
|
|
if (IsEqualGUID( guid, &GUID_SawtoothDown )) return L"GUID_SawtoothDown";
|
|
if (IsEqualGUID( guid, &GUID_Spring )) return L"GUID_Spring";
|
|
if (IsEqualGUID( guid, &GUID_Damper )) return L"GUID_Damper";
|
|
if (IsEqualGUID( guid, &GUID_Inertia )) return L"GUID_Inertia";
|
|
if (IsEqualGUID( guid, &GUID_Friction )) return L"GUID_Friction";
|
|
return L"GUID_Unknown";
|
|
}
|
|
|
|
static const WCHAR *object_usage_to_string( DIDEVICEOBJECTINSTANCEW *instance )
|
|
{
|
|
switch (MAKELONG(instance->wUsage, instance->wUsagePage))
|
|
{
|
|
case MAKELONG(HID_USAGE_DIGITIZER_TIP_PRESSURE, HID_USAGE_PAGE_DIGITIZER): return L"Tip Pressure";
|
|
case MAKELONG(HID_USAGE_CONSUMER_VOLUME, HID_USAGE_PAGE_CONSUMER): return L"Volume";
|
|
|
|
case MAKELONG(HID_USAGE_GENERIC_HATSWITCH, HID_USAGE_PAGE_GENERIC): return L"Hat Switch";
|
|
case MAKELONG(HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_PAGE_GENERIC): return L"Joystick";
|
|
case MAKELONG(HID_USAGE_GENERIC_RX, HID_USAGE_PAGE_GENERIC): return L"X Rotation";
|
|
case MAKELONG(HID_USAGE_GENERIC_RY, HID_USAGE_PAGE_GENERIC): return L"Y Rotation";
|
|
case MAKELONG(HID_USAGE_GENERIC_RZ, HID_USAGE_PAGE_GENERIC): return L"Z Rotation";
|
|
case MAKELONG(HID_USAGE_GENERIC_WHEEL, HID_USAGE_PAGE_GENERIC): return L"Wheel";
|
|
case MAKELONG(HID_USAGE_GENERIC_X, HID_USAGE_PAGE_GENERIC): return L"X Axis";
|
|
case MAKELONG(HID_USAGE_GENERIC_Y, HID_USAGE_PAGE_GENERIC): return L"Y Axis";
|
|
case MAKELONG(HID_USAGE_GENERIC_Z, HID_USAGE_PAGE_GENERIC): return L"Z Axis";
|
|
|
|
case MAKELONG(PID_USAGE_ATTACK_LEVEL, HID_USAGE_PAGE_PID): return L"Attack Level";
|
|
case MAKELONG(PID_USAGE_ATTACK_TIME, HID_USAGE_PAGE_PID): return L"Attack Time";
|
|
case MAKELONG(PID_USAGE_AXES_ENABLE, HID_USAGE_PAGE_PID): return L"Axes Enable";
|
|
|
|
case MAKELONG(PID_USAGE_DC_DEVICE_CONTINUE, HID_USAGE_PAGE_PID): return L"DC Device Continue";
|
|
case MAKELONG(PID_USAGE_DC_DEVICE_PAUSE, HID_USAGE_PAGE_PID): return L"DC Device Pause";
|
|
case MAKELONG(PID_USAGE_DC_DEVICE_RESET, HID_USAGE_PAGE_PID): return L"DC Device Reset";
|
|
case MAKELONG(PID_USAGE_DC_DISABLE_ACTUATORS, HID_USAGE_PAGE_PID): return L"DC Disable Actuators";
|
|
case MAKELONG(PID_USAGE_DC_ENABLE_ACTUATORS, HID_USAGE_PAGE_PID): return L"DC Enable Actuators";
|
|
case MAKELONG(PID_USAGE_DC_STOP_ALL_EFFECTS, HID_USAGE_PAGE_PID): return L"DC Stop All Effects";
|
|
|
|
case MAKELONG(PID_USAGE_DEVICE_GAIN, HID_USAGE_PAGE_PID): return L"Device Gain";
|
|
case MAKELONG(PID_USAGE_DEVICE_GAIN_REPORT, HID_USAGE_PAGE_PID): return L"Device Gain Report";
|
|
case MAKELONG(PID_USAGE_CP_OFFSET, HID_USAGE_PAGE_PID): return L"CP Offset";
|
|
case MAKELONG(PID_USAGE_DEAD_BAND, HID_USAGE_PAGE_PID): return L"Dead Band";
|
|
case MAKELONG(PID_USAGE_DEVICE_CONTROL, HID_USAGE_PAGE_PID): return L"PID Device Control";
|
|
case MAKELONG(PID_USAGE_DEVICE_CONTROL_REPORT, HID_USAGE_PAGE_PID): return L"PID Device Control Report";
|
|
case MAKELONG(PID_USAGE_DIRECTION, HID_USAGE_PAGE_PID): return L"Direction";
|
|
case MAKELONG(PID_USAGE_DIRECTION_ENABLE, HID_USAGE_PAGE_PID): return L"Direction Enable";
|
|
case MAKELONG(PID_USAGE_DURATION, HID_USAGE_PAGE_PID): return L"Duration";
|
|
case MAKELONG(PID_USAGE_EFFECT_BLOCK_INDEX, HID_USAGE_PAGE_PID): return L"Effect Block Index";
|
|
case MAKELONG(PID_USAGE_EFFECT_OPERATION, HID_USAGE_PAGE_PID): return L"Effect Operation";
|
|
case MAKELONG(PID_USAGE_EFFECT_OPERATION_REPORT, HID_USAGE_PAGE_PID): return L"Effect Operation Report";
|
|
case MAKELONG(PID_USAGE_EFFECT_TYPE, HID_USAGE_PAGE_PID): return L"Effect Type";
|
|
|
|
case MAKELONG(PID_USAGE_ET_CONSTANT_FORCE, HID_USAGE_PAGE_PID): return L"ET Constant Force";
|
|
case MAKELONG(PID_USAGE_ET_CUSTOM_FORCE_DATA, HID_USAGE_PAGE_PID): return L"ET Custom Force Data";
|
|
case MAKELONG(PID_USAGE_ET_DAMPER, HID_USAGE_PAGE_PID): return L"ET Damper";
|
|
case MAKELONG(PID_USAGE_ET_FRICTION, HID_USAGE_PAGE_PID): return L"ET Friction";
|
|
case MAKELONG(PID_USAGE_ET_INERTIA, HID_USAGE_PAGE_PID): return L"ET Inertia";
|
|
case MAKELONG(PID_USAGE_ET_RAMP, HID_USAGE_PAGE_PID): return L"ET Ramp";
|
|
case MAKELONG(PID_USAGE_ET_SAWTOOTH_DOWN, HID_USAGE_PAGE_PID): return L"ET Sawtooth Down";
|
|
case MAKELONG(PID_USAGE_ET_SAWTOOTH_UP, HID_USAGE_PAGE_PID): return L"ET Sawtooth Up";
|
|
case MAKELONG(PID_USAGE_ET_SINE, HID_USAGE_PAGE_PID): return L"ET Sine";
|
|
case MAKELONG(PID_USAGE_ET_SPRING, HID_USAGE_PAGE_PID): return L"ET Spring";
|
|
case MAKELONG(PID_USAGE_ET_SQUARE, HID_USAGE_PAGE_PID): return L"ET Square";
|
|
case MAKELONG(PID_USAGE_ET_TRIANGLE, HID_USAGE_PAGE_PID): return L"ET Triangle";
|
|
|
|
case MAKELONG(PID_USAGE_NEGATIVE_COEFFICIENT, HID_USAGE_PAGE_PID): return L"Negative Coefficient";
|
|
case MAKELONG(PID_USAGE_NEGATIVE_SATURATION, HID_USAGE_PAGE_PID): return L"Negative Saturation";
|
|
case MAKELONG(PID_USAGE_POSITIVE_COEFFICIENT, HID_USAGE_PAGE_PID): return L"Positive Coefficient";
|
|
case MAKELONG(PID_USAGE_POSITIVE_SATURATION, HID_USAGE_PAGE_PID): return L"Positive Saturation";
|
|
case MAKELONG(PID_USAGE_SET_CONDITION_REPORT, HID_USAGE_PAGE_PID): return L"Set Condition Report";
|
|
case MAKELONG(PID_USAGE_TYPE_SPECIFIC_BLOCK_OFFSET, HID_USAGE_PAGE_PID): return L"Type Specific Block Offset";
|
|
|
|
case MAKELONG(PID_USAGE_FADE_LEVEL, HID_USAGE_PAGE_PID): return L"Fade Level";
|
|
case MAKELONG(PID_USAGE_FADE_TIME, HID_USAGE_PAGE_PID): return L"Fade Time";
|
|
case MAKELONG(PID_USAGE_LOOP_COUNT, HID_USAGE_PAGE_PID): return L"Loop Count";
|
|
case MAKELONG(PID_USAGE_MAGNITUDE, HID_USAGE_PAGE_PID): return L"Magnitude";
|
|
case MAKELONG(PID_USAGE_OP_EFFECT_START, HID_USAGE_PAGE_PID): return L"Op Effect Start";
|
|
case MAKELONG(PID_USAGE_OP_EFFECT_START_SOLO, HID_USAGE_PAGE_PID): return L"Op Effect Start Solo";
|
|
case MAKELONG(PID_USAGE_OP_EFFECT_STOP, HID_USAGE_PAGE_PID): return L"Op Effect Stop";
|
|
case MAKELONG(PID_USAGE_SET_EFFECT_REPORT, HID_USAGE_PAGE_PID): return L"Set Effect Report";
|
|
case MAKELONG(PID_USAGE_SET_ENVELOPE_REPORT, HID_USAGE_PAGE_PID): return L"Set Envelope Report";
|
|
case MAKELONG(PID_USAGE_SET_PERIODIC_REPORT, HID_USAGE_PAGE_PID): return L"Set Periodic Report";
|
|
case MAKELONG(PID_USAGE_START_DELAY, HID_USAGE_PAGE_PID): return L"Start Delay";
|
|
case MAKELONG(PID_USAGE_STATE_REPORT, HID_USAGE_PAGE_PID): return L"PID State Report";
|
|
case MAKELONG(PID_USAGE_TRIGGER_BUTTON, HID_USAGE_PAGE_PID): return L"Trigger Button";
|
|
|
|
case MAKELONG(HID_USAGE_SIMULATION_RUDDER, HID_USAGE_PAGE_SIMULATION): return L"Rudder";
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
static HRESULT find_next_effect_id( struct hid_joystick *impl, DWORD *index, USAGE type )
|
|
{
|
|
struct pid_device_pool *device_pool = &impl->pid_device_pool;
|
|
struct pid_new_effect *new_effect = &impl->pid_new_effect;
|
|
struct pid_block_load *block_load = &impl->pid_block_load;
|
|
ULONG i, count, report_len = impl->caps.FeatureReportByteLength;
|
|
NTSTATUS status;
|
|
USAGE usage;
|
|
|
|
if (!device_pool->device_managed_caps)
|
|
{
|
|
for (i = 0; i < ARRAY_SIZE(impl->effect_inuse); ++i)
|
|
if (!impl->effect_inuse[i]) break;
|
|
if (i == ARRAY_SIZE(impl->effect_inuse)) return DIERR_DEVICEFULL;
|
|
impl->effect_inuse[i] = TRUE;
|
|
*index = i + 1;
|
|
}
|
|
else
|
|
{
|
|
status = HidP_InitializeReportForID( HidP_Feature, new_effect->id, impl->preparsed,
|
|
impl->feature_report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
count = 1;
|
|
status = HidP_SetUsages( HidP_Feature, HID_USAGE_PAGE_PID, new_effect->type_coll,
|
|
&type, &count, impl->preparsed, impl->feature_report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
if (!HidD_SetFeature( impl->device, impl->feature_report_buf, report_len )) return DIERR_INPUTLOST;
|
|
|
|
status = HidP_InitializeReportForID( HidP_Feature, block_load->id, impl->preparsed,
|
|
impl->feature_report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
if (!HidD_GetFeature( impl->device, impl->feature_report_buf, report_len )) return DIERR_INPUTLOST;
|
|
|
|
count = 1;
|
|
status = HidP_GetUsages( HidP_Feature, HID_USAGE_PAGE_PID, block_load->status_coll,
|
|
&usage, &count, impl->preparsed, impl->feature_report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
if (count != 1 || usage == PID_USAGE_BLOCK_LOAD_ERROR) return DIERR_INPUTLOST;
|
|
if (usage == PID_USAGE_BLOCK_LOAD_FULL) return DIERR_DEVICEFULL;
|
|
|
|
status = HidP_GetUsageValue( HidP_Feature, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
index, impl->preparsed, impl->feature_report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
}
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
typedef BOOL (*enum_object_callback)( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data );
|
|
|
|
static BOOL enum_object( struct hid_joystick *impl, const DIPROPHEADER *filter, DWORD flags,
|
|
enum_object_callback callback, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
if (flags != DIDFT_ALL && !(flags & DIDFT_GETTYPE( instance->dwType ))) return DIENUM_CONTINUE;
|
|
|
|
switch (filter->dwHow)
|
|
{
|
|
case DIPH_DEVICE:
|
|
return callback( impl, caps, instance, data );
|
|
case DIPH_BYOFFSET:
|
|
if (filter->dwObj != instance->dwOfs) return DIENUM_CONTINUE;
|
|
return callback( impl, caps, instance, data );
|
|
case DIPH_BYID:
|
|
if ((filter->dwObj & 0x00ffffff) != (instance->dwType & 0x00ffffff)) return DIENUM_CONTINUE;
|
|
return callback( impl, caps, instance, data );
|
|
case DIPH_BYUSAGE:
|
|
if (HIWORD( filter->dwObj ) != instance->wUsagePage) return DIENUM_CONTINUE;
|
|
if (LOWORD( filter->dwObj ) != instance->wUsage) return DIENUM_CONTINUE;
|
|
return callback( impl, caps, instance, data );
|
|
default:
|
|
FIXME( "unimplemented filter dwHow %#x dwObj %#x\n", filter->dwHow, filter->dwObj );
|
|
break;
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static void check_pid_effect_axis_caps( struct hid_joystick *impl, DIDEVICEOBJECTINSTANCEW *instance )
|
|
{
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < effect_update->axis_count; ++i)
|
|
{
|
|
if (effect_update->axis_caps[i]->usage_page != instance->wUsagePage) continue;
|
|
if (effect_update->axis_caps[i]->usage_min > instance->wUsage) continue;
|
|
if (effect_update->axis_caps[i]->usage_max >= instance->wUsage) break;
|
|
}
|
|
|
|
if (i == effect_update->axis_count) return;
|
|
instance->dwType |= DIDFT_FFACTUATOR;
|
|
instance->dwFlags |= DIDOI_FFACTUATOR;
|
|
}
|
|
|
|
static void set_axis_type( DIDEVICEOBJECTINSTANCEW *instance, BOOL *seen, DWORD i, DWORD *count )
|
|
{
|
|
if (!seen[i]) instance->dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( i );
|
|
else instance->dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 6 + (*count)++ );
|
|
seen[i] = TRUE;
|
|
}
|
|
|
|
static BOOL enum_objects( struct hid_joystick *impl, const DIPROPHEADER *filter, DWORD flags,
|
|
enum_object_callback callback, void *data )
|
|
{
|
|
DWORD collection = 0, object = 0, axis = 0, button = 0, pov = 0, value_ofs = 0, button_ofs = 0, j, count, len;
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
DIDEVICEOBJECTINSTANCEW instance = {.dwSize = sizeof(DIDEVICEOBJECTINSTANCEW)};
|
|
struct hid_value_caps *caps, *caps_end, *nary, *nary_end, *effect_caps;
|
|
struct hid_collection_node *node, *node_end;
|
|
WORD version = impl->base.dinput->dwVersion;
|
|
BOOL ret, seen_axis[6] = {0};
|
|
const WCHAR *tmp;
|
|
|
|
button_ofs += impl->caps.NumberInputValueCaps * sizeof(LONG);
|
|
if (version >= 0x800)
|
|
{
|
|
button_ofs += impl->caps.NumberOutputValueCaps * sizeof(LONG);
|
|
button_ofs += impl->caps.NumberFeatureValueCaps * sizeof(LONG);
|
|
}
|
|
|
|
for (caps = HID_INPUT_VALUE_CAPS( preparsed ), caps_end = caps + preparsed->input_caps_count;
|
|
caps != caps_end; ++caps)
|
|
{
|
|
if (!caps->usage_page) continue;
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) continue;
|
|
|
|
if (caps->usage_page == HID_USAGE_PAGE_PID)
|
|
{
|
|
value_ofs += (caps->usage_max - caps->usage_min + 1) * sizeof(LONG);
|
|
object += caps->usage_max - caps->usage_min + 1;
|
|
}
|
|
else if (caps->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
value_ofs += (caps->usage_max - caps->usage_min + 1) * sizeof(LONG);
|
|
else for (j = caps->usage_min; j <= caps->usage_max; ++j)
|
|
{
|
|
instance.dwOfs = value_ofs;
|
|
switch (MAKELONG(j, caps->usage_page))
|
|
{
|
|
case MAKELONG(HID_USAGE_GENERIC_X, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_Y, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_Z, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_RX, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_RY, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_RZ, HID_USAGE_PAGE_GENERIC):
|
|
set_axis_type( &instance, seen_axis, j - HID_USAGE_GENERIC_X, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_SIMULATION_STEERING, HID_USAGE_PAGE_SIMULATION):
|
|
set_axis_type( &instance, seen_axis, 0, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_SIMULATION_ACCELERATOR, HID_USAGE_PAGE_SIMULATION):
|
|
set_axis_type( &instance, seen_axis, 1, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_GENERIC_WHEEL, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_SIMULATION_THROTTLE, HID_USAGE_PAGE_SIMULATION):
|
|
set_axis_type( &instance, seen_axis, 2, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_SIMULATION_RUDDER, HID_USAGE_PAGE_SIMULATION):
|
|
case MAKELONG(HID_USAGE_SIMULATION_BRAKE, HID_USAGE_PAGE_SIMULATION):
|
|
set_axis_type( &instance, seen_axis, 5, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_GENERIC_HATSWITCH, HID_USAGE_PAGE_GENERIC):
|
|
instance.dwType = DIDFT_POV | DIDFT_MAKEINSTANCE( pov++ );
|
|
instance.dwFlags = 0;
|
|
break;
|
|
case MAKELONG(HID_USAGE_GENERIC_SLIDER, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_DIAL, HID_USAGE_PAGE_GENERIC):
|
|
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 6 + axis++ );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
default:
|
|
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 6 + axis++ );
|
|
instance.dwFlags = 0;
|
|
break;
|
|
}
|
|
instance.wUsagePage = caps->usage_page;
|
|
instance.wUsage = j;
|
|
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
|
|
instance.wReportId = caps->report_id;
|
|
instance.wCollectionNumber = caps->link_collection;
|
|
instance.dwDimension = caps->units;
|
|
instance.wExponent = caps->units_exp;
|
|
if ((tmp = object_usage_to_string( &instance ))) lstrcpynW( instance.tszName, tmp, MAX_PATH );
|
|
else swprintf( instance.tszName, MAX_PATH, L"Unknown %u", DIDFT_GETINSTANCE( instance.dwType ) );
|
|
check_pid_effect_axis_caps( impl, &instance );
|
|
ret = enum_object( impl, filter, flags, callback, caps, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
value_ofs += sizeof(LONG);
|
|
object++;
|
|
}
|
|
}
|
|
|
|
effect_caps = impl->pid_effect_update.trigger_button_caps;
|
|
|
|
for (caps = HID_INPUT_VALUE_CAPS( preparsed ), caps_end = caps + preparsed->input_caps_count;
|
|
caps != caps_end; ++caps)
|
|
{
|
|
if (!caps->usage_page) continue;
|
|
if (!(caps->flags & HID_VALUE_CAPS_IS_BUTTON)) continue;
|
|
|
|
if (caps->usage_page == HID_USAGE_PAGE_PID)
|
|
{
|
|
button_ofs += caps->usage_max - caps->usage_min + 1;
|
|
object += caps->usage_max - caps->usage_min + 1;
|
|
}
|
|
else if (caps->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
button_ofs += caps->usage_max - caps->usage_min + 1;
|
|
else for (j = caps->usage_min; j <= caps->usage_max; ++j)
|
|
{
|
|
instance.dwOfs = button_ofs;
|
|
instance.dwType = DIDFT_PSHBUTTON | DIDFT_MAKEINSTANCE( button++ );
|
|
instance.dwFlags = 0;
|
|
if (effect_caps && effect_caps->logical_min <= j && effect_caps->logical_max >= j)
|
|
{
|
|
instance.dwType |= DIDFT_FFEFFECTTRIGGER;
|
|
instance.dwFlags |= DIDOI_FFEFFECTTRIGGER;
|
|
}
|
|
instance.wUsagePage = caps->usage_page;
|
|
instance.wUsage = j;
|
|
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
|
|
instance.wReportId = caps->report_id;
|
|
instance.wCollectionNumber = caps->link_collection;
|
|
instance.dwDimension = caps->units;
|
|
instance.wExponent = caps->units_exp;
|
|
swprintf( instance.tszName, MAX_PATH, L"Button %u", DIDFT_GETINSTANCE( instance.dwType ) );
|
|
ret = enum_object( impl, filter, flags, callback, caps, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
button_ofs++;
|
|
object++;
|
|
}
|
|
}
|
|
|
|
count = preparsed->output_caps_count + preparsed->feature_caps_count;
|
|
for (caps = HID_OUTPUT_VALUE_CAPS( preparsed ), caps_end = caps + count;
|
|
caps != caps_end; ++caps)
|
|
{
|
|
if (!caps->usage_page) continue;
|
|
|
|
if (caps->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
{
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) button_ofs += caps->usage_max - caps->usage_min + 1;
|
|
else value_ofs += (caps->usage_max - caps->usage_min + 1) * sizeof(LONG);
|
|
}
|
|
else if (caps->flags & HID_VALUE_CAPS_ARRAY_HAS_MORE)
|
|
{
|
|
for (nary_end = caps - 1; caps != caps_end; caps++)
|
|
if (!(caps->flags & HID_VALUE_CAPS_ARRAY_HAS_MORE)) break;
|
|
|
|
for (nary = caps; nary != nary_end; nary--)
|
|
{
|
|
if (version < 0x800) instance.dwOfs = 0;
|
|
else instance.dwOfs = button_ofs;
|
|
|
|
instance.dwType = DIDFT_NODATA | DIDFT_MAKEINSTANCE( object++ ) | DIDFT_OUTPUT;
|
|
instance.dwFlags = 0x80008000;
|
|
instance.wUsagePage = nary->usage_page;
|
|
instance.wUsage = nary->usage_min;
|
|
instance.guidType = GUID_Unknown;
|
|
instance.wReportId = nary->report_id;
|
|
instance.wCollectionNumber = nary->link_collection;
|
|
instance.dwDimension = caps->units;
|
|
instance.wExponent = caps->units_exp;
|
|
if ((tmp = object_usage_to_string( &instance ))) lstrcpynW( instance.tszName, tmp, MAX_PATH );
|
|
else swprintf( instance.tszName, MAX_PATH, L"Unknown %u", DIDFT_GETINSTANCE( instance.dwType ) );
|
|
ret = enum_object( impl, filter, flags, callback, nary, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
button_ofs++;
|
|
}
|
|
}
|
|
else for (j = caps->usage_min; j <= caps->usage_max; ++j)
|
|
{
|
|
if (version < 0x800) instance.dwOfs = 0;
|
|
else if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) instance.dwOfs = button_ofs;
|
|
else instance.dwOfs = value_ofs;
|
|
|
|
instance.dwType = DIDFT_NODATA | DIDFT_MAKEINSTANCE( object++ ) | DIDFT_OUTPUT;
|
|
instance.dwFlags = 0x80008000;
|
|
instance.wUsagePage = caps->usage_page;
|
|
instance.wUsage = j;
|
|
instance.guidType = GUID_Unknown;
|
|
instance.wReportId = caps->report_id;
|
|
instance.wCollectionNumber = caps->link_collection;
|
|
instance.dwDimension = caps->units;
|
|
instance.wExponent = caps->units_exp;
|
|
if ((tmp = object_usage_to_string( &instance ))) lstrcpynW( instance.tszName, tmp, MAX_PATH );
|
|
else swprintf( instance.tszName, MAX_PATH, L"Unknown %u", DIDFT_GETINSTANCE( instance.dwType ) );
|
|
ret = enum_object( impl, filter, flags, callback, caps, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) button_ofs++;
|
|
else value_ofs += sizeof(LONG);
|
|
}
|
|
}
|
|
|
|
for (node = HID_COLLECTION_NODES( preparsed ), node_end = node + preparsed->number_link_collection_nodes;
|
|
node != node_end; ++node)
|
|
{
|
|
if (!node->usage_page) continue;
|
|
if (node->usage_page < HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
{
|
|
instance.dwOfs = 0;
|
|
instance.dwType = DIDFT_COLLECTION | DIDFT_MAKEINSTANCE( collection++ ) | DIDFT_NODATA;
|
|
instance.dwFlags = 0;
|
|
instance.wUsagePage = node->usage_page;
|
|
instance.wUsage = node->usage;
|
|
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
|
|
instance.wReportId = 0;
|
|
instance.wCollectionNumber = node->parent;
|
|
instance.dwDimension = 0;
|
|
instance.wExponent = 0;
|
|
len = swprintf( instance.tszName, MAX_PATH, L"Collection %u - ", DIDFT_GETINSTANCE( instance.dwType ) );
|
|
if ((tmp = object_usage_to_string( &instance ))) lstrcpynW( instance.tszName + len, tmp, MAX_PATH - len );
|
|
else swprintf( instance.tszName + len, MAX_PATH - len, L"Unknown %u", DIDFT_GETINSTANCE( instance.dwType ) );
|
|
ret = enum_object( impl, filter, flags, callback, NULL, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
}
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static void set_report_value( struct hid_joystick *impl, char *report_buf,
|
|
struct hid_value_caps *caps, LONG value )
|
|
{
|
|
ULONG report_len = impl->caps.OutputReportByteLength;
|
|
PHIDP_PREPARSED_DATA preparsed = impl->preparsed;
|
|
LONG log_min, log_max, phy_min, phy_max;
|
|
NTSTATUS status;
|
|
|
|
if (!caps) return;
|
|
|
|
log_min = caps->logical_min;
|
|
log_max = caps->logical_max;
|
|
phy_min = caps->physical_min;
|
|
phy_max = caps->physical_max;
|
|
|
|
if (phy_max || phy_min)
|
|
{
|
|
if (value > phy_max || value < phy_min) value = -1;
|
|
else value = log_min + (value - phy_min) * (log_max - log_min) / (phy_max - phy_min);
|
|
}
|
|
|
|
status = HidP_SetUsageValue( HidP_Output, caps->usage_page, caps->link_collection,
|
|
caps->usage_min, value, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue %04x:%04x returned %#x\n",
|
|
caps->usage_page, caps->usage_min, status );
|
|
}
|
|
|
|
static void hid_joystick_addref( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->internal_ref );
|
|
TRACE( "iface %p, internal ref %u.\n", iface, ref );
|
|
}
|
|
|
|
static void hid_joystick_release( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->internal_ref );
|
|
TRACE( "iface %p, internal ref %u.\n", iface, ref );
|
|
|
|
if (!ref)
|
|
{
|
|
free( impl->usages_buf );
|
|
free( impl->feature_report_buf );
|
|
free( impl->output_report_buf );
|
|
free( impl->input_report_buf );
|
|
HidD_FreePreparsedData( impl->preparsed );
|
|
CloseHandle( impl->base.read_event );
|
|
CloseHandle( impl->device );
|
|
dinput_device_destroy( iface );
|
|
}
|
|
}
|
|
|
|
static HRESULT hid_joystick_get_property( IDirectInputDevice8W *iface, DWORD property,
|
|
DIPROPHEADER *header, const DIDEVICEOBJECTINSTANCEW *instance )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
|
|
switch (property)
|
|
{
|
|
case (DWORD_PTR)DIPROP_PRODUCTNAME:
|
|
{
|
|
DIPROPSTRING *value = (DIPROPSTRING *)header;
|
|
lstrcpynW( value->wsz, impl->base.instance.tszProductName, MAX_PATH );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_INSTANCENAME:
|
|
{
|
|
DIPROPSTRING *value = (DIPROPSTRING *)header;
|
|
lstrcpynW( value->wsz, impl->base.instance.tszInstanceName, MAX_PATH );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_VIDPID:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (!impl->attrs.VendorID || !impl->attrs.ProductID) return DIERR_UNSUPPORTED;
|
|
value->dwData = MAKELONG( impl->attrs.VendorID, impl->attrs.ProductID );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_JOYSTICKID:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
value->dwData = impl->base.instance.guidInstance.Data3;
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_GUIDANDPATH:
|
|
{
|
|
DIPROPGUIDANDPATH *value = (DIPROPGUIDANDPATH *)header;
|
|
lstrcpynW( value->wszPath, impl->device_path, MAX_PATH );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_FFLOAD:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (!(impl->base.caps.dwFlags & DIDC_FORCEFEEDBACK)) return DIERR_UNSUPPORTED;
|
|
if (!impl->base.acquired || !(impl->base.dwCoopLevel & DISCL_EXCLUSIVE)) return DIERR_NOTEXCLUSIVEACQUIRED;
|
|
value->dwData = 0;
|
|
return DI_OK;
|
|
}
|
|
}
|
|
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT hid_joystick_send_device_gain( IDirectInputDevice8W *iface, LONG device_gain )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct pid_device_gain *report = &impl->pid_device_gain;
|
|
ULONG report_len = impl->caps.OutputReportByteLength;
|
|
char *report_buf = impl->output_report_buf;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
if (!report->id || !report->device_gain_caps) return DI_OK;
|
|
|
|
status = HidP_InitializeReportForID( HidP_Output, report->id, impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
set_report_value( impl, report_buf, report->device_gain_caps, device_gain );
|
|
|
|
if (!WriteFile( impl->device, report_buf, report_len, NULL, NULL )) return DIERR_INPUTLOST;
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT hid_joystick_acquire( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG report_len = impl->caps.InputReportByteLength;
|
|
BOOL ret;
|
|
|
|
if (impl->device == INVALID_HANDLE_VALUE)
|
|
{
|
|
impl->device = CreateFileW( impl->device_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 );
|
|
if (impl->device == INVALID_HANDLE_VALUE) return DIERR_INPUTLOST;
|
|
}
|
|
|
|
memset( &impl->read_ovl, 0, sizeof(impl->read_ovl) );
|
|
impl->read_ovl.hEvent = impl->base.read_event;
|
|
ret = ReadFile( impl->device, impl->input_report_buf, report_len, NULL, &impl->read_ovl );
|
|
if (!ret && GetLastError() != ERROR_IO_PENDING)
|
|
{
|
|
CloseHandle( impl->device );
|
|
impl->device = INVALID_HANDLE_VALUE;
|
|
return DIERR_INPUTLOST;
|
|
}
|
|
|
|
IDirectInputDevice8_SendForceFeedbackCommand( iface, DISFFC_RESET );
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT hid_joystick_send_force_feedback_command( IDirectInputDevice8W *iface, DWORD command, BOOL unacquire );
|
|
|
|
static HRESULT hid_joystick_unacquire( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
BOOL ret;
|
|
|
|
if (impl->device == INVALID_HANDLE_VALUE) return DI_NOEFFECT;
|
|
|
|
ret = CancelIoEx( impl->device, &impl->read_ovl );
|
|
if (!ret) WARN( "CancelIoEx failed, last error %u\n", GetLastError() );
|
|
else WaitForSingleObject( impl->base.read_event, INFINITE );
|
|
|
|
if (!(impl->base.caps.dwFlags & DIDC_FORCEFEEDBACK)) return DI_OK;
|
|
if (!impl->base.acquired || !(impl->base.dwCoopLevel & DISCL_EXCLUSIVE)) return DI_OK;
|
|
hid_joystick_send_force_feedback_command( iface, DISFFC_RESET, TRUE );
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT hid_joystick_create_effect( IDirectInputDevice8W *iface, IDirectInputEffect **out );
|
|
|
|
static HRESULT hid_joystick_get_effect_info( IDirectInputDevice8W *iface, DIEFFECTINFOW *info, const GUID *guid )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
struct pid_set_condition *set_condition = &impl->pid_set_condition;
|
|
struct pid_set_periodic *set_periodic = &impl->pid_set_periodic;
|
|
struct pid_set_envelope *set_envelope = &impl->pid_set_envelope;
|
|
PHIDP_PREPARSED_DATA preparsed = impl->preparsed;
|
|
HIDP_BUTTON_CAPS button;
|
|
ULONG type, collection;
|
|
NTSTATUS status;
|
|
USAGE usage = 0;
|
|
USHORT count;
|
|
|
|
switch ((usage = effect_guid_to_usage( guid )))
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
type = DIEFT_PERIODIC;
|
|
break;
|
|
case PID_USAGE_ET_SPRING:
|
|
case PID_USAGE_ET_DAMPER:
|
|
case PID_USAGE_ET_INERTIA:
|
|
case PID_USAGE_ET_FRICTION:
|
|
type = DIEFT_CONDITION;
|
|
break;
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
type = DIEFT_CONSTANTFORCE;
|
|
break;
|
|
case PID_USAGE_ET_RAMP:
|
|
type = DIEFT_RAMPFORCE;
|
|
break;
|
|
case PID_USAGE_ET_CUSTOM_FORCE_DATA:
|
|
type = DIEFT_CUSTOMFORCE;
|
|
break;
|
|
default:
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
|
|
if (!(collection = effect_update->collection)) return DIERR_DEVICENOTREG;
|
|
|
|
info->dwDynamicParams = DIEP_TYPESPECIFICPARAMS;
|
|
if (effect_update->axis_count) info->dwDynamicParams |= DIEP_AXES;
|
|
if (effect_update->duration_caps) info->dwDynamicParams |= DIEP_DURATION;
|
|
if (effect_update->gain_caps) info->dwDynamicParams |= DIEP_GAIN;
|
|
if (effect_update->sample_period_caps) info->dwDynamicParams |= DIEP_SAMPLEPERIOD;
|
|
if (effect_update->start_delay_caps)
|
|
{
|
|
type |= DIEFT_STARTDELAY;
|
|
info->dwDynamicParams |= DIEP_STARTDELAY;
|
|
}
|
|
if (effect_update->direction_coll) info->dwDynamicParams |= DIEP_DIRECTION;
|
|
if (effect_update->axes_coll) info->dwDynamicParams |= DIEP_AXES;
|
|
|
|
if (!(collection = effect_update->type_coll)) return DIERR_DEVICENOTREG;
|
|
else
|
|
{
|
|
count = 1;
|
|
status = HidP_GetSpecificButtonCaps( HidP_Output, HID_USAGE_PAGE_PID, collection,
|
|
usage, &button, &count, preparsed );
|
|
if (status != HIDP_STATUS_SUCCESS)
|
|
{
|
|
WARN( "HidP_GetSpecificButtonCaps %#x returned %#x\n", usage, status );
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
else if (!count)
|
|
{
|
|
WARN( "effect usage %#x not found\n", usage );
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
}
|
|
|
|
if ((type & DIEFT_PERIODIC) && (collection = set_periodic->collection))
|
|
{
|
|
if (set_periodic->magnitude_caps) info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
if (set_periodic->offset_caps) info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
if (set_periodic->period_caps) info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
if (set_periodic->phase_caps) info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
}
|
|
|
|
if ((type & (DIEFT_PERIODIC | DIEFT_RAMPFORCE | DIEFT_CONSTANTFORCE)) && (collection = set_envelope->collection))
|
|
{
|
|
info->dwDynamicParams |= DIEP_ENVELOPE;
|
|
if (set_envelope->attack_level_caps) type |= DIEFT_FFATTACK;
|
|
if (set_envelope->attack_time_caps) type |= DIEFT_FFATTACK;
|
|
if (set_envelope->fade_level_caps) type |= DIEFT_FFFADE;
|
|
if (set_envelope->fade_time_caps) type |= DIEFT_FFFADE;
|
|
if (effect_update->trigger_button_caps) info->dwDynamicParams |= DIEP_TRIGGERBUTTON;
|
|
if (effect_update->trigger_repeat_interval_caps) info->dwDynamicParams |= DIEP_TRIGGERREPEATINTERVAL;
|
|
}
|
|
|
|
if ((type & DIEFT_CONDITION) && (collection = set_condition->collection))
|
|
{
|
|
if (set_condition->center_point_offset_caps)
|
|
info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
if (set_condition->positive_coefficient_caps || set_condition->negative_coefficient_caps)
|
|
info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
if (set_condition->positive_saturation_caps || set_condition->negative_saturation_caps)
|
|
{
|
|
info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
type |= DIEFT_SATURATION;
|
|
}
|
|
if (set_condition->dead_band_caps)
|
|
{
|
|
info->dwDynamicParams |= DIEP_TYPESPECIFICPARAMS;
|
|
type |= DIEFT_DEADBAND;
|
|
}
|
|
}
|
|
|
|
info->guid = *guid;
|
|
info->dwEffType = type;
|
|
info->dwStaticParams = info->dwDynamicParams;
|
|
lstrcpynW( info->tszName, effect_guid_to_string( guid ), MAX_PATH );
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL CALLBACK unload_effect_object( IDirectInputEffect *effect, void *context )
|
|
{
|
|
IDirectInputEffect_Unload( effect );
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT hid_joystick_send_force_feedback_command( IDirectInputDevice8W *iface, DWORD command, BOOL unacquire )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct pid_control_report *report = &impl->pid_device_control;
|
|
ULONG report_len = impl->caps.OutputReportByteLength;
|
|
char *report_buf = impl->output_report_buf;
|
|
NTSTATUS status;
|
|
USAGE usage;
|
|
ULONG count;
|
|
|
|
TRACE( "iface %p, flags %x.\n", iface, command );
|
|
|
|
switch (command)
|
|
{
|
|
case DISFFC_RESET: usage = PID_USAGE_DC_DEVICE_RESET; break;
|
|
case DISFFC_STOPALL: usage = PID_USAGE_DC_STOP_ALL_EFFECTS; break;
|
|
case DISFFC_PAUSE: usage = PID_USAGE_DC_DEVICE_PAUSE; break;
|
|
case DISFFC_CONTINUE: usage = PID_USAGE_DC_DEVICE_CONTINUE; break;
|
|
case DISFFC_SETACTUATORSON: usage = PID_USAGE_DC_ENABLE_ACTUATORS; break;
|
|
case DISFFC_SETACTUATORSOFF: usage = PID_USAGE_DC_DISABLE_ACTUATORS; break;
|
|
}
|
|
|
|
if (command == DISFFC_RESET) IDirectInputDevice8_EnumCreatedEffectObjects( iface, unload_effect_object, NULL, 0 );
|
|
|
|
count = 1;
|
|
status = HidP_InitializeReportForID( HidP_Output, report->id, impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
status = HidP_SetUsages( HidP_Output, HID_USAGE_PAGE_PID, report->control_coll, &usage,
|
|
&count, impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return status;
|
|
|
|
if (!WriteFile( impl->device, report_buf, report_len, NULL, NULL )) return DIERR_INPUTLOST;
|
|
if (!unacquire) hid_joystick_send_device_gain( iface, impl->base.device_gain );
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT hid_joystick_enum_created_effect_objects( IDirectInputDevice8W *iface,
|
|
LPDIENUMCREATEDEFFECTOBJECTSCALLBACK callback,
|
|
void *context, DWORD flags )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct hid_joystick_effect *effect, *next;
|
|
|
|
TRACE( "iface %p, callback %p, context %p, flags %#x.\n", iface, callback, context, flags );
|
|
|
|
LIST_FOR_EACH_ENTRY_SAFE(effect, next, &impl->effect_list, struct hid_joystick_effect, entry)
|
|
if (callback( &effect->IDirectInputEffect_iface, context ) != DIENUM_CONTINUE) break;
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
struct parse_device_state_params
|
|
{
|
|
BYTE old_state[DEVICE_STATE_MAX_SIZE];
|
|
BYTE buttons[128];
|
|
DWORD time;
|
|
DWORD seq;
|
|
};
|
|
|
|
static BOOL check_device_state_button( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
IDirectInputDevice8W *iface = &impl->base.IDirectInputDevice8W_iface;
|
|
struct parse_device_state_params *params = data;
|
|
BYTE old_value, value;
|
|
|
|
if (instance->wReportId != impl->base.device_state_report_id) return DIENUM_CONTINUE;
|
|
|
|
value = params->buttons[instance->wUsage - 1];
|
|
old_value = params->old_state[instance->dwOfs];
|
|
impl->base.device_state[instance->dwOfs] = value;
|
|
if (old_value != value)
|
|
queue_event( iface, instance->dwType, value, params->time, params->seq );
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static LONG sign_extend( ULONG value, struct object_properties *properties )
|
|
{
|
|
UINT sign = 1 << (properties->bit_size - 1);
|
|
if (sign <= 1 || properties->logical_min >= 0) return value;
|
|
return value - ((value & sign) << 1);
|
|
}
|
|
|
|
static LONG scale_value( ULONG value, struct object_properties *properties )
|
|
{
|
|
LONG tmp = sign_extend( value, properties ), log_min, log_max, phy_min, phy_max;
|
|
log_min = properties->logical_min;
|
|
log_max = properties->logical_max;
|
|
phy_min = properties->range_min;
|
|
phy_max = properties->range_max;
|
|
|
|
if (log_min > tmp || log_max < tmp) return -1; /* invalid / null value */
|
|
return phy_min + MulDiv( tmp - log_min, phy_max - phy_min, log_max - log_min );
|
|
}
|
|
|
|
static LONG scale_axis_value( ULONG value, struct object_properties *properties )
|
|
{
|
|
LONG tmp = sign_extend( value, properties ), log_ctr, log_min, log_max, phy_ctr, phy_min, phy_max;
|
|
ULONG bit_max = (1 << properties->bit_size) - 1;
|
|
|
|
log_min = properties->logical_min;
|
|
log_max = properties->logical_max;
|
|
phy_min = properties->range_min;
|
|
phy_max = properties->range_max;
|
|
/* xinput HID gamepad have bogus logical value range, let's use the bit range instead */
|
|
if (log_min == 0 && log_max == -1) log_max = bit_max;
|
|
|
|
if (phy_min == 0) phy_ctr = phy_max >> 1;
|
|
else phy_ctr = round( (phy_min + phy_max) / 2.0 );
|
|
if (log_min == 0) log_ctr = log_max >> 1;
|
|
else log_ctr = round( (log_min + log_max) / 2.0 );
|
|
|
|
tmp -= log_ctr;
|
|
if (tmp <= 0)
|
|
{
|
|
log_max = MulDiv( log_min - log_ctr, properties->deadzone, 10000 );
|
|
log_min = MulDiv( log_min - log_ctr, properties->saturation, 10000 );
|
|
phy_max = phy_ctr;
|
|
}
|
|
else
|
|
{
|
|
log_min = MulDiv( log_max - log_ctr, properties->deadzone, 10000 );
|
|
log_max = MulDiv( log_max - log_ctr, properties->saturation, 10000 );
|
|
phy_min = phy_ctr;
|
|
}
|
|
|
|
if (tmp <= log_min) return phy_min;
|
|
if (tmp >= log_max) return phy_max;
|
|
return phy_min + MulDiv( tmp - log_min, phy_max - phy_min, log_max - log_min );
|
|
}
|
|
|
|
static BOOL read_device_state_value( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct object_properties *properties = impl->base.object_properties + instance->dwOfs / sizeof(LONG);
|
|
IDirectInputDevice8W *iface = &impl->base.IDirectInputDevice8W_iface;
|
|
ULONG logical_value, report_len = impl->caps.InputReportByteLength;
|
|
struct parse_device_state_params *params = data;
|
|
char *report_buf = impl->input_report_buf;
|
|
LONG old_value, value;
|
|
NTSTATUS status;
|
|
|
|
if (instance->wReportId != impl->base.device_state_report_id) return DIENUM_CONTINUE;
|
|
|
|
status = HidP_GetUsageValue( HidP_Input, instance->wUsagePage, 0, instance->wUsage,
|
|
&logical_value, impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsageValue %04x:%04x returned %#x\n",
|
|
instance->wUsagePage, instance->wUsage, status );
|
|
if (instance->dwType & DIDFT_AXIS) value = scale_axis_value( logical_value, properties );
|
|
else value = scale_value( logical_value, properties );
|
|
|
|
old_value = *(LONG *)(params->old_state + instance->dwOfs);
|
|
*(LONG *)(impl->base.device_state + instance->dwOfs) = value;
|
|
if (old_value != value)
|
|
queue_event( iface, instance->dwType, value, params->time, params->seq );
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT hid_joystick_read( IDirectInputDevice8W *iface )
|
|
{
|
|
static const DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(filter),
|
|
.dwHeaderSize = sizeof(filter),
|
|
.dwHow = DIPH_DEVICE,
|
|
};
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG i, count, report_len = impl->caps.InputReportByteLength;
|
|
DIDATAFORMAT *format = impl->base.device_format;
|
|
struct parse_device_state_params params = {{0}};
|
|
char *report_buf = impl->input_report_buf;
|
|
USAGE_AND_PAGE *usages;
|
|
NTSTATUS status;
|
|
HRESULT hr;
|
|
BOOL ret;
|
|
|
|
ret = GetOverlappedResult( impl->device, &impl->read_ovl, &count, FALSE );
|
|
if (ret && TRACE_ON(dinput))
|
|
{
|
|
TRACE( "read size %u report:\n", count );
|
|
for (i = 0; i < count;)
|
|
{
|
|
char buffer[256], *buf = buffer;
|
|
buf += sprintf(buf, "%08x ", i);
|
|
do
|
|
{
|
|
buf += sprintf(buf, " %02x", (BYTE)report_buf[i] );
|
|
} while (++i % 16 && i < count);
|
|
TRACE("%s\n", buffer);
|
|
}
|
|
}
|
|
|
|
EnterCriticalSection( &impl->base.crit );
|
|
while (ret)
|
|
{
|
|
count = impl->usages_count;
|
|
memset( impl->usages_buf, 0, count * sizeof(*impl->usages_buf) );
|
|
status = HidP_GetUsagesEx( HidP_Input, 0, impl->usages_buf, &count,
|
|
impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsagesEx returned %#x\n", status );
|
|
|
|
if (report_buf[0] == impl->base.device_state_report_id)
|
|
{
|
|
params.time = GetCurrentTime();
|
|
params.seq = impl->base.dinput->evsequence++;
|
|
memcpy( params.old_state, impl->base.device_state, format->dwDataSize );
|
|
memset( impl->base.device_state, 0, format->dwDataSize );
|
|
|
|
while (count--)
|
|
{
|
|
usages = impl->usages_buf + count;
|
|
if (usages->UsagePage != HID_USAGE_PAGE_BUTTON)
|
|
FIXME( "unimplemented usage page %x.\n", usages->UsagePage );
|
|
else if (usages->Usage >= 128)
|
|
FIXME( "ignoring extraneous button %d.\n", usages->Usage );
|
|
else
|
|
params.buttons[usages->Usage - 1] = 0x80;
|
|
}
|
|
|
|
enum_objects( impl, &filter, DIDFT_AXIS | DIDFT_POV, read_device_state_value, ¶ms );
|
|
enum_objects( impl, &filter, DIDFT_BUTTON, check_device_state_button, ¶ms );
|
|
if (impl->base.hEvent && memcmp( ¶ms.old_state, impl->base.device_state, format->dwDataSize ))
|
|
SetEvent( impl->base.hEvent );
|
|
}
|
|
|
|
memset( &impl->read_ovl, 0, sizeof(impl->read_ovl) );
|
|
impl->read_ovl.hEvent = impl->base.read_event;
|
|
ret = ReadFile( impl->device, report_buf, report_len, &count, &impl->read_ovl );
|
|
}
|
|
|
|
if (GetLastError() == ERROR_IO_PENDING || GetLastError() == ERROR_OPERATION_ABORTED) hr = DI_OK;
|
|
else
|
|
{
|
|
WARN( "GetOverlappedResult/ReadFile failed, error %u\n", GetLastError() );
|
|
CloseHandle(impl->device);
|
|
impl->device = INVALID_HANDLE_VALUE;
|
|
hr = DIERR_INPUTLOST;
|
|
}
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
struct enum_objects_params
|
|
{
|
|
LPDIENUMDEVICEOBJECTSCALLBACKW callback;
|
|
void *context;
|
|
};
|
|
|
|
static BOOL enum_objects_callback( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct enum_objects_params *params = data;
|
|
return params->callback( instance, params->context );
|
|
}
|
|
|
|
static HRESULT hid_joystick_enum_objects( IDirectInputDevice8W *iface, const DIPROPHEADER *filter,
|
|
DWORD flags, LPDIENUMDEVICEOBJECTSCALLBACKW callback, void *context )
|
|
{
|
|
struct enum_objects_params params = {.callback = callback, .context = context};
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
return enum_objects( impl, filter, flags, enum_objects_callback, ¶ms );
|
|
}
|
|
|
|
static const struct dinput_device_vtbl hid_joystick_vtbl =
|
|
{
|
|
hid_joystick_release,
|
|
NULL,
|
|
hid_joystick_read,
|
|
hid_joystick_acquire,
|
|
hid_joystick_unacquire,
|
|
hid_joystick_enum_objects,
|
|
hid_joystick_get_property,
|
|
hid_joystick_get_effect_info,
|
|
hid_joystick_create_effect,
|
|
hid_joystick_send_force_feedback_command,
|
|
hid_joystick_send_device_gain,
|
|
hid_joystick_enum_created_effect_objects,
|
|
};
|
|
|
|
static DWORD device_type_for_version( DWORD type, DWORD version )
|
|
{
|
|
if (version >= 0x0800) return type;
|
|
|
|
switch (GET_DIDEVICE_TYPE( type ))
|
|
{
|
|
case DI8DEVTYPE_JOYSTICK:
|
|
if (GET_DIDEVICE_SUBTYPE( type ) == DI8DEVTYPEJOYSTICK_LIMITED)
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_GAMEPAD:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_GAMEPAD << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_DRIVING:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_WHEEL << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_FLIGHT:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_FLIGHTSTICK << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_SUPPLEMENTAL:
|
|
if (GET_DIDEVICE_SUBTYPE( type ) == DI8DEVTYPESUPPLEMENTAL_HEADTRACKER)
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_HEADTRACKER << 8) | DIDEVTYPE_HID;
|
|
if (GET_DIDEVICE_SUBTYPE( type ) == DI8DEVTYPESUPPLEMENTAL_RUDDERPEDALS)
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_RUDDER << 8) | DIDEVTYPE_HID;
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_1STPERSON:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
default:
|
|
return DIDEVTYPE_DEVICE | DIDEVTYPE_HID;
|
|
}
|
|
}
|
|
|
|
static BOOL hid_joystick_device_try_open( UINT32 handle, const WCHAR *path, HANDLE *device,
|
|
PHIDP_PREPARSED_DATA *preparsed, HIDD_ATTRIBUTES *attrs,
|
|
HIDP_CAPS *caps, DIDEVICEINSTANCEW *instance, DWORD version )
|
|
{
|
|
PHIDP_PREPARSED_DATA preparsed_data = NULL;
|
|
DWORD type = 0, button_count = 0;
|
|
HIDP_BUTTON_CAPS buttons[10];
|
|
HIDP_VALUE_CAPS value;
|
|
HANDLE device_file;
|
|
NTSTATUS status;
|
|
USHORT count;
|
|
|
|
device_file = CreateFileW( path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 );
|
|
if (device_file == INVALID_HANDLE_VALUE) return FALSE;
|
|
|
|
if (!HidD_GetPreparsedData( device_file, &preparsed_data )) goto failed;
|
|
if (!HidD_GetAttributes( device_file, attrs )) goto failed;
|
|
if (HidP_GetCaps( preparsed_data, caps ) != HIDP_STATUS_SUCCESS) goto failed;
|
|
|
|
if (caps->UsagePage == HID_USAGE_PAGE_GAME) FIXME( "game usage page not implemented!\n" );
|
|
if (caps->UsagePage == HID_USAGE_PAGE_SIMULATION) FIXME( "simulation usage page not implemented!\n" );
|
|
if (caps->UsagePage != HID_USAGE_PAGE_GENERIC) goto failed;
|
|
if (caps->Usage != HID_USAGE_GENERIC_GAMEPAD && caps->Usage != HID_USAGE_GENERIC_JOYSTICK) goto failed;
|
|
|
|
if (!HidD_GetProductString( device_file, instance->tszInstanceName, MAX_PATH * sizeof(WCHAR) )) goto failed;
|
|
if (!HidD_GetProductString( device_file, instance->tszProductName, MAX_PATH * sizeof(WCHAR) )) goto failed;
|
|
|
|
instance->guidInstance = hid_joystick_guid;
|
|
instance->guidInstance.Data1 ^= handle;
|
|
instance->guidProduct = dinput_pidvid_guid;
|
|
instance->guidProduct.Data1 = MAKELONG( attrs->VendorID, attrs->ProductID );
|
|
instance->guidFFDriver = GUID_NULL;
|
|
instance->wUsagePage = caps->UsagePage;
|
|
instance->wUsage = caps->Usage;
|
|
|
|
count = ARRAY_SIZE(buttons);
|
|
status = HidP_GetSpecificButtonCaps( HidP_Output, HID_USAGE_PAGE_PID, 0,
|
|
PID_USAGE_DC_DEVICE_RESET, buttons, &count, preparsed_data );
|
|
if (status == HIDP_STATUS_SUCCESS && count > 0)
|
|
instance->guidFFDriver = IID_IDirectInputPIDDriver;
|
|
|
|
count = ARRAY_SIZE(buttons);
|
|
status = HidP_GetSpecificButtonCaps( HidP_Input, HID_USAGE_PAGE_BUTTON, 0, 0, buttons, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS) count = button_count = 0;
|
|
while (count--)
|
|
{
|
|
if (!buttons[count].IsRange) button_count += 1;
|
|
else button_count += buttons[count].Range.UsageMax - buttons[count].Range.UsageMin + 1;
|
|
}
|
|
|
|
switch (caps->Usage)
|
|
{
|
|
case HID_USAGE_GENERIC_GAMEPAD:
|
|
type = DI8DEVTYPE_GAMEPAD | DIDEVTYPE_HID;
|
|
if (button_count < 6) type |= DI8DEVTYPEGAMEPAD_LIMITED << 8;
|
|
else type |= DI8DEVTYPEGAMEPAD_STANDARD << 8;
|
|
break;
|
|
case HID_USAGE_GENERIC_JOYSTICK:
|
|
type = DI8DEVTYPE_JOYSTICK | DIDEVTYPE_HID;
|
|
if (button_count < 5) type |= DI8DEVTYPEJOYSTICK_LIMITED << 8;
|
|
else type |= DI8DEVTYPEJOYSTICK_STANDARD << 8;
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0,
|
|
HID_USAGE_GENERIC_Z, &value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_LIMITED << 8) | DIDEVTYPE_HID;
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0,
|
|
HID_USAGE_GENERIC_HATSWITCH, &value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_LIMITED << 8) | DIDEVTYPE_HID;
|
|
|
|
break;
|
|
}
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X,
|
|
&value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_SUPPLEMENTAL | (DI8DEVTYPESUPPLEMENTAL_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Y,
|
|
&value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_SUPPLEMENTAL | (DI8DEVTYPESUPPLEMENTAL_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
instance->dwDevType = device_type_for_version( type, version );
|
|
|
|
*device = device_file;
|
|
*preparsed = preparsed_data;
|
|
return TRUE;
|
|
|
|
failed:
|
|
CloseHandle( device_file );
|
|
HidD_FreePreparsedData( preparsed_data );
|
|
return FALSE;
|
|
}
|
|
|
|
static HRESULT hid_joystick_device_open( int index, DIDEVICEINSTANCEW *filter, WCHAR *device_path,
|
|
HANDLE *device, PHIDP_PREPARSED_DATA *preparsed,
|
|
HIDD_ATTRIBUTES *attrs, HIDP_CAPS *caps, DWORD version )
|
|
{
|
|
char buffer[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + MAX_PATH * sizeof(WCHAR)];
|
|
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail = (void *)buffer;
|
|
SP_DEVICE_INTERFACE_DATA iface = {.cbSize = sizeof(iface)};
|
|
SP_DEVINFO_DATA devinfo = {.cbSize = sizeof(devinfo)};
|
|
DIDEVICEINSTANCEW instance = *filter;
|
|
WCHAR device_id[MAX_PATH], *tmp;
|
|
HDEVINFO set, xi_set;
|
|
UINT32 i = 0, handle;
|
|
BOOL override;
|
|
DWORD type;
|
|
GUID hid;
|
|
|
|
TRACE( "index %d, product %s, instance %s\n", index, debugstr_guid( &filter->guidProduct ),
|
|
debugstr_guid( &filter->guidInstance ) );
|
|
|
|
HidD_GetHidGuid( &hid );
|
|
|
|
set = SetupDiGetClassDevsW( &hid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT );
|
|
if (set == INVALID_HANDLE_VALUE) return DIERR_DEVICENOTREG;
|
|
xi_set = SetupDiGetClassDevsW( &GUID_DEVINTERFACE_WINEXINPUT, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT );
|
|
|
|
*device = NULL;
|
|
*preparsed = NULL;
|
|
while (SetupDiEnumDeviceInterfaces( set, NULL, &hid, i++, &iface ))
|
|
{
|
|
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
|
if (!SetupDiGetDeviceInterfaceDetailW( set, &iface, detail, sizeof(buffer), NULL, &devinfo ))
|
|
continue;
|
|
if (!SetupDiGetDevicePropertyW( set, &devinfo, &DEVPROPKEY_HID_HANDLE, &type,
|
|
(BYTE *)&handle, sizeof(handle), NULL, 0 ) ||
|
|
type != DEVPROP_TYPE_UINT32)
|
|
continue;
|
|
if (!hid_joystick_device_try_open( handle, detail->DevicePath, device, preparsed,
|
|
attrs, caps, &instance, version ))
|
|
continue;
|
|
|
|
if (device_instance_is_disabled( &instance, &override ))
|
|
goto next;
|
|
|
|
if (override)
|
|
{
|
|
if (!SetupDiGetDeviceInstanceIdW( set, &devinfo, device_id, MAX_PATH, NULL ) ||
|
|
!(tmp = wcsstr( device_id, L"&IG_" )))
|
|
goto next;
|
|
memcpy( tmp, L"&XI_", sizeof(L"&XI_") - sizeof(WCHAR) );
|
|
if (!SetupDiOpenDeviceInfoW( xi_set, device_id, NULL, 0, &devinfo ))
|
|
goto next;
|
|
if (!SetupDiEnumDeviceInterfaces( xi_set, &devinfo, &GUID_DEVINTERFACE_WINEXINPUT, 0, &iface ))
|
|
goto next;
|
|
if (!SetupDiGetDeviceInterfaceDetailW( xi_set, &iface, detail, sizeof(buffer), NULL, &devinfo ))
|
|
goto next;
|
|
|
|
CloseHandle( *device );
|
|
HidD_FreePreparsedData( *preparsed );
|
|
if (!hid_joystick_device_try_open( handle, detail->DevicePath, device, preparsed,
|
|
attrs, caps, &instance, version ))
|
|
continue;
|
|
}
|
|
|
|
/* enumerate device by GUID */
|
|
if (index < 0 && IsEqualGUID( &filter->guidProduct, &instance.guidProduct )) break;
|
|
if (index < 0 && IsEqualGUID( &filter->guidInstance, &instance.guidInstance )) break;
|
|
|
|
/* enumerate all devices */
|
|
if (index >= 0 && !index--) break;
|
|
|
|
next:
|
|
CloseHandle( *device );
|
|
HidD_FreePreparsedData( *preparsed );
|
|
*device = NULL;
|
|
*preparsed = NULL;
|
|
}
|
|
|
|
if (xi_set != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList( xi_set );
|
|
SetupDiDestroyDeviceInfoList( set );
|
|
if (!*device || !*preparsed) return DIERR_DEVICENOTREG;
|
|
|
|
lstrcpynW( device_path, detail->DevicePath, MAX_PATH );
|
|
*filter = instance;
|
|
return DI_OK;
|
|
}
|
|
|
|
HRESULT hid_joystick_enum_device( DWORD type, DWORD flags, DIDEVICEINSTANCEW *instance, DWORD version, int index )
|
|
{
|
|
HIDD_ATTRIBUTES attrs = {.Size = sizeof(attrs)};
|
|
PHIDP_PREPARSED_DATA preparsed;
|
|
WCHAR device_path[MAX_PATH];
|
|
HIDP_CAPS caps;
|
|
HANDLE device;
|
|
HRESULT hr;
|
|
|
|
TRACE( "type %#x, flags %#x, instance %p, version %#04x, index %d\n", type, flags, instance, version, index );
|
|
|
|
hr = hid_joystick_device_open( index, instance, device_path, &device, &preparsed,
|
|
&attrs, &caps, version );
|
|
if (hr != DI_OK) return hr;
|
|
|
|
HidD_FreePreparsedData( preparsed );
|
|
CloseHandle( device );
|
|
|
|
TRACE( "found device %s, usage %04x:%04x, product %s, instance %s, name %s\n", debugstr_w(device_path),
|
|
instance->wUsagePage, instance->wUsage, debugstr_guid( &instance->guidProduct ),
|
|
debugstr_guid( &instance->guidInstance ), debugstr_w(instance->tszInstanceName) );
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL init_object_properties( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct object_properties *properties = impl->base.object_properties + instance->dwOfs / sizeof(LONG);
|
|
LONG tmp;
|
|
|
|
properties->bit_size = caps->bit_size;
|
|
properties->physical_min = caps->physical_min;
|
|
properties->physical_max = caps->physical_max;
|
|
properties->logical_min = caps->logical_min;
|
|
properties->logical_max = caps->logical_max;
|
|
|
|
if (instance->dwType & DIDFT_AXIS) properties->range_max = 65535;
|
|
else
|
|
{
|
|
properties->range_max = 36000;
|
|
tmp = caps->logical_max - caps->logical_min;
|
|
if (tmp > 0) properties->range_max -= 36000 / (tmp + 1);
|
|
}
|
|
|
|
properties->saturation = 10000;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL init_pid_reports( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct pid_set_constant_force *set_constant_force = &impl->pid_set_constant_force;
|
|
struct pid_set_ramp_force *set_ramp_force = &impl->pid_set_ramp_force;
|
|
struct pid_control_report *device_control = &impl->pid_device_control;
|
|
struct pid_control_report *effect_control = &impl->pid_effect_control;
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
struct pid_set_condition *set_condition = &impl->pid_set_condition;
|
|
struct pid_set_periodic *set_periodic = &impl->pid_set_periodic;
|
|
struct pid_set_envelope *set_envelope = &impl->pid_set_envelope;
|
|
struct pid_device_gain *device_gain = &impl->pid_device_gain;
|
|
struct pid_device_pool *device_pool = &impl->pid_device_pool;
|
|
struct pid_block_free *block_free = &impl->pid_block_free;
|
|
struct pid_block_load *block_load = &impl->pid_block_load;
|
|
struct pid_new_effect *new_effect = &impl->pid_new_effect;
|
|
|
|
#define SET_COLLECTION( rep ) \
|
|
do \
|
|
{ \
|
|
if (rep->collection) FIXME( "duplicate " #rep " report!\n" ); \
|
|
else rep->collection = DIDFT_GETINSTANCE( instance->dwType ); \
|
|
} while (0)
|
|
|
|
#define SET_SUB_COLLECTION( rep, sub ) \
|
|
do { \
|
|
if (instance->wCollectionNumber != rep->collection) \
|
|
FIXME( "unexpected " #rep "." #sub " parent!\n" ); \
|
|
else if (rep->sub) \
|
|
FIXME( "duplicate " #rep "." #sub " collection!\n" ); \
|
|
else \
|
|
rep->sub = DIDFT_GETINSTANCE( instance->dwType ); \
|
|
} while (0)
|
|
|
|
if (instance->wUsagePage == HID_USAGE_PAGE_PID)
|
|
{
|
|
switch (instance->wUsage)
|
|
{
|
|
case PID_USAGE_DEVICE_CONTROL_REPORT: SET_COLLECTION( device_control ); break;
|
|
case PID_USAGE_EFFECT_OPERATION_REPORT: SET_COLLECTION( effect_control ); break;
|
|
case PID_USAGE_SET_EFFECT_REPORT: SET_COLLECTION( effect_update ); break;
|
|
case PID_USAGE_SET_PERIODIC_REPORT: SET_COLLECTION( set_periodic ); break;
|
|
case PID_USAGE_SET_ENVELOPE_REPORT: SET_COLLECTION( set_envelope ); break;
|
|
case PID_USAGE_SET_CONDITION_REPORT: SET_COLLECTION( set_condition ); break;
|
|
case PID_USAGE_SET_CONSTANT_FORCE_REPORT: SET_COLLECTION( set_constant_force ); break;
|
|
case PID_USAGE_SET_RAMP_FORCE_REPORT: SET_COLLECTION( set_ramp_force ); break;
|
|
case PID_USAGE_DEVICE_GAIN_REPORT: SET_COLLECTION( device_gain ); break;
|
|
case PID_USAGE_POOL_REPORT: SET_COLLECTION( device_pool ); break;
|
|
case PID_USAGE_BLOCK_FREE_REPORT: SET_COLLECTION( block_free ); break;
|
|
case PID_USAGE_BLOCK_LOAD_REPORT: SET_COLLECTION( block_load ); break;
|
|
case PID_USAGE_CREATE_NEW_EFFECT_REPORT: SET_COLLECTION( new_effect ); break;
|
|
|
|
case PID_USAGE_DEVICE_CONTROL: SET_SUB_COLLECTION( device_control, control_coll ); break;
|
|
case PID_USAGE_EFFECT_OPERATION: SET_SUB_COLLECTION( effect_control, control_coll ); break;
|
|
case PID_USAGE_EFFECT_TYPE:
|
|
if (instance->wCollectionNumber == effect_update->collection)
|
|
SET_SUB_COLLECTION( effect_update, type_coll );
|
|
else if (instance->wCollectionNumber == new_effect->collection)
|
|
SET_SUB_COLLECTION( new_effect, type_coll );
|
|
break;
|
|
case PID_USAGE_AXES_ENABLE: SET_SUB_COLLECTION( effect_update, axes_coll ); break;
|
|
case PID_USAGE_DIRECTION: SET_SUB_COLLECTION( effect_update, direction_coll ); break;
|
|
case PID_USAGE_BLOCK_LOAD_STATUS: SET_SUB_COLLECTION( block_load, status_coll ); break;
|
|
}
|
|
}
|
|
|
|
#undef SET_SUB_COLLECTION
|
|
#undef SET_COLLECTION
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL init_pid_caps( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct pid_set_constant_force *set_constant_force = &impl->pid_set_constant_force;
|
|
struct pid_set_ramp_force *set_ramp_force = &impl->pid_set_ramp_force;
|
|
struct pid_control_report *device_control = &impl->pid_device_control;
|
|
struct pid_control_report *effect_control = &impl->pid_effect_control;
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
struct pid_set_condition *set_condition = &impl->pid_set_condition;
|
|
struct pid_set_periodic *set_periodic = &impl->pid_set_periodic;
|
|
struct pid_set_envelope *set_envelope = &impl->pid_set_envelope;
|
|
struct pid_device_gain *device_gain = &impl->pid_device_gain;
|
|
struct pid_device_pool *device_pool = &impl->pid_device_pool;
|
|
struct pid_block_free *block_free = &impl->pid_block_free;
|
|
struct pid_block_load *block_load = &impl->pid_block_load;
|
|
struct pid_new_effect *new_effect = &impl->pid_new_effect;
|
|
|
|
if (!(instance->dwType & DIDFT_OUTPUT)) return DIENUM_CONTINUE;
|
|
|
|
#define SET_REPORT_ID( rep ) \
|
|
do \
|
|
{ \
|
|
if (!rep->id) \
|
|
rep->id = instance->wReportId; \
|
|
else if (rep->id != instance->wReportId) \
|
|
FIXME( "multiple " #rep " report ids!\n" ); \
|
|
} while (0)
|
|
|
|
if (instance->wCollectionNumber == device_control->control_coll)
|
|
SET_REPORT_ID( device_control );
|
|
if (instance->wCollectionNumber == effect_control->control_coll)
|
|
SET_REPORT_ID( effect_control );
|
|
if (instance->wCollectionNumber == effect_update->type_coll)
|
|
SET_REPORT_ID( effect_update );
|
|
if (instance->wCollectionNumber == effect_update->collection)
|
|
{
|
|
SET_REPORT_ID( effect_update );
|
|
if (instance->wUsage == PID_USAGE_DURATION)
|
|
effect_update->duration_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_GAIN)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
effect_update->gain_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_SAMPLE_PERIOD)
|
|
effect_update->sample_period_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_START_DELAY)
|
|
effect_update->start_delay_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_TRIGGER_BUTTON)
|
|
effect_update->trigger_button_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_TRIGGER_REPEAT_INTERVAL)
|
|
effect_update->trigger_repeat_interval_caps = caps;
|
|
}
|
|
if (instance->wCollectionNumber == effect_update->axes_coll)
|
|
{
|
|
SET_REPORT_ID( effect_update );
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 36000;
|
|
if (effect_update->axis_count >= 6) FIXME( "more than 6 PID axes detected\n" );
|
|
else effect_update->axis_caps[effect_update->axis_count] = caps;
|
|
effect_update->axis_count++;
|
|
}
|
|
if (instance->wCollectionNumber == effect_update->direction_coll)
|
|
{
|
|
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" );
|
|
else effect_update->direction_caps[effect_update->direction_count] = caps;
|
|
effect_update->direction_count++;
|
|
}
|
|
if (instance->wCollectionNumber == set_periodic->collection)
|
|
{
|
|
SET_REPORT_ID( set_periodic );
|
|
if (instance->wUsage == PID_USAGE_MAGNITUDE)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
set_periodic->magnitude_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_PERIOD)
|
|
set_periodic->period_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_PHASE)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 35900;
|
|
set_periodic->phase_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_OFFSET)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_periodic->offset_caps = caps;
|
|
}
|
|
}
|
|
if (instance->wCollectionNumber == set_envelope->collection)
|
|
{
|
|
SET_REPORT_ID( set_envelope );
|
|
if (instance->wUsage == PID_USAGE_ATTACK_LEVEL)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
set_envelope->attack_level_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_ATTACK_TIME)
|
|
set_envelope->attack_time_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_FADE_LEVEL)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
set_envelope->fade_level_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_FADE_TIME)
|
|
set_envelope->fade_time_caps = caps;
|
|
}
|
|
if (instance->wCollectionNumber == set_condition->collection)
|
|
{
|
|
SET_REPORT_ID( set_condition );
|
|
if (instance->wUsage == PID_USAGE_CP_OFFSET)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_condition->center_point_offset_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_POSITIVE_COEFFICIENT)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_condition->positive_coefficient_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_NEGATIVE_COEFFICIENT)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_condition->negative_coefficient_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_POSITIVE_SATURATION)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
set_condition->positive_saturation_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_NEGATIVE_SATURATION)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
set_condition->negative_saturation_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_DEAD_BAND)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
set_condition->dead_band_caps = caps;
|
|
}
|
|
}
|
|
if (instance->wCollectionNumber == set_constant_force->collection)
|
|
{
|
|
SET_REPORT_ID( set_constant_force );
|
|
if (instance->wUsage == PID_USAGE_MAGNITUDE)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_constant_force->magnitude_caps = caps;
|
|
}
|
|
}
|
|
if (instance->wCollectionNumber == set_ramp_force->collection)
|
|
{
|
|
SET_REPORT_ID( set_ramp_force );
|
|
if (instance->wUsage == PID_USAGE_RAMP_START)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_ramp_force->start_caps = caps;
|
|
}
|
|
if (instance->wUsage == PID_USAGE_RAMP_END)
|
|
{
|
|
caps->physical_min = -10000;
|
|
caps->physical_max = 10000;
|
|
set_ramp_force->end_caps = caps;
|
|
}
|
|
}
|
|
if (instance->wCollectionNumber == device_gain->collection)
|
|
{
|
|
SET_REPORT_ID( device_gain );
|
|
if (instance->wUsage == PID_USAGE_DEVICE_GAIN)
|
|
{
|
|
caps->physical_min = 0;
|
|
caps->physical_max = 10000;
|
|
device_gain->device_gain_caps = caps;
|
|
}
|
|
}
|
|
if (instance->wCollectionNumber == device_pool->collection)
|
|
{
|
|
SET_REPORT_ID( device_pool );
|
|
if (instance->wUsage == PID_USAGE_DEVICE_MANAGED_POOL)
|
|
device_pool->device_managed_caps = caps;
|
|
}
|
|
if (instance->wCollectionNumber == block_free->collection)
|
|
SET_REPORT_ID( block_free );
|
|
if (instance->wCollectionNumber == block_load->collection)
|
|
SET_REPORT_ID( block_load );
|
|
if (instance->wCollectionNumber == block_load->status_coll)
|
|
SET_REPORT_ID( block_load );
|
|
if (instance->wCollectionNumber == new_effect->collection)
|
|
SET_REPORT_ID( new_effect );
|
|
if (instance->wCollectionNumber == new_effect->type_coll)
|
|
SET_REPORT_ID( new_effect );
|
|
|
|
#undef SET_REPORT_ID
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID *guid, IDirectInputDevice8W **out )
|
|
{
|
|
static const DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(filter),
|
|
.dwHeaderSize = sizeof(filter),
|
|
.dwHow = DIPH_DEVICE,
|
|
};
|
|
DIDEVICEINSTANCEW instance =
|
|
{
|
|
.dwSize = sizeof(instance),
|
|
.guidProduct = *guid,
|
|
.guidInstance = *guid
|
|
};
|
|
DIPROPRANGE range =
|
|
{
|
|
.diph =
|
|
{
|
|
.dwSize = sizeof(range),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_DEVICE,
|
|
},
|
|
};
|
|
HIDD_ATTRIBUTES attrs = {.Size = sizeof(attrs)};
|
|
struct object_properties *object_properties;
|
|
struct hid_preparsed_data *preparsed;
|
|
struct hid_joystick *impl = NULL;
|
|
USAGE_AND_PAGE *usages;
|
|
char *buffer;
|
|
HRESULT hr;
|
|
DWORD size;
|
|
|
|
TRACE( "dinput %p, guid %s, out %p\n", dinput, debugstr_guid( guid ), out );
|
|
|
|
*out = NULL;
|
|
instance.guidProduct.Data1 = dinput_pidvid_guid.Data1;
|
|
instance.guidInstance.Data1 = hid_joystick_guid.Data1;
|
|
if (IsEqualGUID( &dinput_pidvid_guid, &instance.guidProduct ))
|
|
instance.guidProduct = *guid;
|
|
else if (IsEqualGUID( &hid_joystick_guid, &instance.guidInstance ))
|
|
instance.guidInstance = *guid;
|
|
else
|
|
return DIERR_DEVICENOTREG;
|
|
|
|
hr = dinput_device_alloc( sizeof(struct hid_joystick), &hid_joystick_vtbl, guid, dinput, (void **)&impl );
|
|
if (FAILED(hr)) return hr;
|
|
impl->base.crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": hid_joystick.base.crit");
|
|
impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
|
|
impl->base.read_event = CreateEventW( NULL, TRUE, FALSE, NULL );
|
|
|
|
hr = hid_joystick_device_open( -1, &instance, impl->device_path, &impl->device, &impl->preparsed,
|
|
&attrs, &impl->caps, dinput->dwVersion );
|
|
if (hr != DI_OK) goto failed;
|
|
|
|
impl->internal_ref = 1;
|
|
impl->base.instance = instance;
|
|
impl->base.caps.dwDevType = instance.dwDevType;
|
|
impl->attrs = attrs;
|
|
list_init( &impl->effect_list );
|
|
|
|
preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
size = preparsed->input_caps_count * sizeof(struct object_properties);
|
|
if (!(object_properties = calloc( 1, size ))) goto failed;
|
|
impl->base.object_properties = object_properties;
|
|
enum_objects( impl, &filter, DIDFT_AXIS | DIDFT_POV, init_object_properties, NULL );
|
|
|
|
size = impl->caps.InputReportByteLength;
|
|
if (!(buffer = malloc( size ))) goto failed;
|
|
impl->input_report_buf = buffer;
|
|
size = impl->caps.OutputReportByteLength;
|
|
if (!(buffer = malloc( size ))) goto failed;
|
|
impl->output_report_buf = buffer;
|
|
size = impl->caps.FeatureReportByteLength;
|
|
if (!(buffer = malloc( size ))) goto failed;
|
|
impl->feature_report_buf = buffer;
|
|
impl->usages_count = HidP_MaxUsageListLength( HidP_Input, 0, impl->preparsed );
|
|
size = impl->usages_count * sizeof(USAGE_AND_PAGE);
|
|
if (!(usages = malloc( size ))) goto failed;
|
|
impl->usages_buf = usages;
|
|
|
|
enum_objects( impl, &filter, DIDFT_COLLECTION, init_pid_reports, NULL );
|
|
enum_objects( impl, &filter, DIDFT_NODATA, init_pid_caps, NULL );
|
|
|
|
TRACE( "device control id %u, coll %u, control coll %u\n", impl->pid_device_control.id,
|
|
impl->pid_device_control.collection, impl->pid_device_control.control_coll );
|
|
TRACE( "effect control id %u, coll %u\n", impl->pid_effect_control.id, impl->pid_effect_control.collection );
|
|
TRACE( "effect update id %u, coll %u, type_coll %u\n", impl->pid_effect_update.id,
|
|
impl->pid_effect_update.collection, impl->pid_effect_update.type_coll );
|
|
TRACE( "set periodic id %u, coll %u\n", impl->pid_set_periodic.id, impl->pid_set_periodic.collection );
|
|
TRACE( "set envelope id %u, coll %u\n", impl->pid_set_envelope.id, impl->pid_set_envelope.collection );
|
|
TRACE( "set condition id %u, coll %u\n", impl->pid_set_condition.id, impl->pid_set_condition.collection );
|
|
TRACE( "set constant force id %u, coll %u\n", impl->pid_set_constant_force.id,
|
|
impl->pid_set_constant_force.collection );
|
|
TRACE( "set ramp force id %u, coll %u\n", impl->pid_set_ramp_force.id, impl->pid_set_ramp_force.collection );
|
|
TRACE( "device gain id %u, coll %u\n", impl->pid_device_gain.id, impl->pid_device_gain.collection );
|
|
TRACE( "device pool id %u, coll %u\n", impl->pid_device_pool.id, impl->pid_device_pool.collection );
|
|
TRACE( "block free id %u, coll %u\n", impl->pid_block_free.id, impl->pid_block_free.collection );
|
|
TRACE( "block load id %u, coll %u, status_coll %u\n", impl->pid_block_load.id,
|
|
impl->pid_block_load.collection, impl->pid_block_load.status_coll );
|
|
TRACE( "create new effect id %u, coll %u, type_coll %u\n", impl->pid_new_effect.id,
|
|
impl->pid_new_effect.collection, impl->pid_new_effect.type_coll );
|
|
|
|
if (impl->pid_device_control.id)
|
|
{
|
|
impl->base.caps.dwFlags |= DIDC_FORCEFEEDBACK;
|
|
if (impl->pid_effect_update.start_delay_caps)
|
|
impl->base.caps.dwFlags |= DIDC_STARTDELAY;
|
|
if (impl->pid_set_envelope.attack_level_caps ||
|
|
impl->pid_set_envelope.attack_time_caps)
|
|
impl->base.caps.dwFlags |= DIDC_FFATTACK;
|
|
if (impl->pid_set_envelope.fade_level_caps ||
|
|
impl->pid_set_envelope.fade_time_caps)
|
|
impl->base.caps.dwFlags |= DIDC_FFFADE;
|
|
if (impl->pid_set_condition.positive_saturation_caps ||
|
|
impl->pid_set_condition.negative_saturation_caps)
|
|
impl->base.caps.dwFlags |= DIDC_SATURATION;
|
|
if (impl->pid_set_condition.dead_band_caps)
|
|
impl->base.caps.dwFlags |= DIDC_DEADBAND;
|
|
impl->base.caps.dwFFSamplePeriod = 1000000;
|
|
impl->base.caps.dwFFMinTimeResolution = 1000000;
|
|
impl->base.caps.dwHardwareRevision = 1;
|
|
impl->base.caps.dwFFDriverVersion = 1;
|
|
}
|
|
|
|
if (FAILED(hr = dinput_device_init( &impl->base.IDirectInputDevice8W_iface ))) goto failed;
|
|
|
|
*out = &impl->base.IDirectInputDevice8W_iface;
|
|
return DI_OK;
|
|
|
|
failed:
|
|
IDirectInputDevice_Release( &impl->base.IDirectInputDevice8W_iface );
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_QueryInterface( IDirectInputEffect *iface, REFIID iid, void **out )
|
|
{
|
|
TRACE( "iface %p, iid %s, out %p\n", iface, debugstr_guid( iid ), out );
|
|
|
|
if (IsEqualGUID( iid, &IID_IUnknown ) ||
|
|
IsEqualGUID( iid, &IID_IDirectInputEffect ))
|
|
{
|
|
IDirectInputEffect_AddRef( iface );
|
|
*out = iface;
|
|
return S_OK;
|
|
}
|
|
|
|
FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) );
|
|
*out = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI hid_joystick_effect_AddRef( IDirectInputEffect *iface )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->ref );
|
|
TRACE( "iface %p, ref %u.\n", iface, ref );
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI hid_joystick_effect_Release( IDirectInputEffect *iface )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->ref );
|
|
TRACE( "iface %p, ref %u.\n", iface, ref );
|
|
if (!ref)
|
|
{
|
|
IDirectInputEffect_Unload( iface );
|
|
EnterCriticalSection( &impl->joystick->base.crit );
|
|
list_remove( &impl->entry );
|
|
LeaveCriticalSection( &impl->joystick->base.crit );
|
|
hid_joystick_release( &impl->joystick->base.IDirectInputDevice8W_iface );
|
|
free( impl->set_envelope_buf );
|
|
free( impl->type_specific_buf );
|
|
free( impl->effect_update_buf );
|
|
free( impl->effect_control_buf );
|
|
free( impl );
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Initialize( IDirectInputEffect *iface, HINSTANCE inst,
|
|
DWORD version, REFGUID guid )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
struct hid_joystick *joystick = impl->joystick;
|
|
ULONG count, report_len = joystick->caps.OutputReportByteLength;
|
|
NTSTATUS status;
|
|
USAGE type;
|
|
|
|
TRACE( "iface %p, inst %p, version %u, guid %s\n", iface, inst, version, debugstr_guid( guid ) );
|
|
|
|
if (!inst) return DIERR_INVALIDPARAM;
|
|
if (!guid) return E_POINTER;
|
|
if (!(type = effect_guid_to_usage( guid ))) return DIERR_DEVICENOTREG;
|
|
|
|
status = HidP_InitializeReportForID( HidP_Output, joystick->pid_effect_update.id,
|
|
joystick->preparsed, impl->effect_update_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
|
|
impl->type_specific_buf[0] = 0;
|
|
impl->set_envelope_buf[0] = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
status = HidP_InitializeReportForID( HidP_Output, joystick->pid_set_periodic.id,
|
|
joystick->preparsed, impl->type_specific_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
break;
|
|
case PID_USAGE_ET_SPRING:
|
|
case PID_USAGE_ET_DAMPER:
|
|
case PID_USAGE_ET_INERTIA:
|
|
case PID_USAGE_ET_FRICTION:
|
|
status = HidP_InitializeReportForID( HidP_Output, joystick->pid_set_condition.id, joystick->preparsed,
|
|
impl->type_specific_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
break;
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
status = HidP_InitializeReportForID( HidP_Output, joystick->pid_set_constant_force.id, joystick->preparsed,
|
|
impl->type_specific_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
break;
|
|
case PID_USAGE_ET_RAMP:
|
|
status = HidP_InitializeReportForID( HidP_Output, joystick->pid_set_ramp_force.id, joystick->preparsed,
|
|
impl->type_specific_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
break;
|
|
case PID_USAGE_ET_CUSTOM_FORCE_DATA:
|
|
FIXME( "effect type %#x not implemented!\n", type );
|
|
break;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
case PID_USAGE_ET_RAMP:
|
|
status = HidP_InitializeReportForID( HidP_Output, joystick->pid_set_envelope.id, joystick->preparsed,
|
|
impl->set_envelope_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
break;
|
|
}
|
|
|
|
count = 1;
|
|
status = HidP_SetUsages( HidP_Output, HID_USAGE_PAGE_PID, joystick->pid_effect_update.type_coll,
|
|
&type, &count, joystick->preparsed, impl->effect_update_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) return DIERR_DEVICENOTREG;
|
|
|
|
impl->type = type;
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_GetEffectGuid( IDirectInputEffect *iface, GUID *guid )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
|
|
TRACE( "iface %p, guid %p.\n", iface, guid );
|
|
|
|
if (!guid) return E_POINTER;
|
|
*guid = *effect_usage_to_guid( impl->type );
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL get_parameters_object_id( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
*(DWORD *)data = instance->dwType;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static BOOL get_parameters_object_ofs( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIDATAFORMAT *device_format = impl->base.device_format, *user_format = impl->base.user_format;
|
|
DIOBJECTDATAFORMAT *device_obj, *user_obj;
|
|
|
|
if (!user_format) return DIENUM_CONTINUE;
|
|
|
|
user_obj = user_format->rgodf + device_format->dwNumObjs;
|
|
device_obj = device_format->rgodf + device_format->dwNumObjs;
|
|
while (user_obj-- > user_format->rgodf && device_obj-- > device_format->rgodf)
|
|
{
|
|
if (!user_obj->dwType) continue;
|
|
if (device_obj->dwType == instance->dwType) break;
|
|
}
|
|
if (user_obj < user_format->rgodf) return DIENUM_CONTINUE;
|
|
|
|
*(DWORD *)data = user_obj->dwOfs;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static void convert_directions_to_spherical( const DIEFFECT *in, DIEFFECT *out )
|
|
{
|
|
DWORD i, j, direction_flags = DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL;
|
|
double tmp;
|
|
|
|
switch (in->dwFlags & direction_flags)
|
|
{
|
|
case DIEFF_CARTESIAN:
|
|
for (i = 1; i < in->cAxes; ++i)
|
|
{
|
|
tmp = in->rglDirection[0];
|
|
for (j = 1; j < i; ++j) tmp = sqrt( tmp * tmp + in->rglDirection[j] * in->rglDirection[j] );
|
|
tmp = atan2( in->rglDirection[i], tmp );
|
|
out->rglDirection[i - 1] = tmp * 18000 / M_PI;
|
|
}
|
|
out->rglDirection[in->cAxes - 1] = 0;
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
case DIEFF_POLAR:
|
|
out->rglDirection[0] = (in->rglDirection[0] % 36000) - 9000;
|
|
if (out->rglDirection[0] < 0) out->rglDirection[0] += 36000;
|
|
for (i = 1; i < in->cAxes; ++i) out->rglDirection[i] = 0;
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
case DIEFF_SPHERICAL:
|
|
for (i = 0; i < in->cAxes - 1; ++i)
|
|
{
|
|
out->rglDirection[i] = in->rglDirection[i] % 36000;
|
|
if (out->rglDirection[i] < 0) out->rglDirection[i] += 36000;
|
|
}
|
|
out->rglDirection[i] = 0;
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void convert_directions_from_spherical( const DIEFFECT *in, DIEFFECT *out )
|
|
{
|
|
DWORD i, j, direction_flags = DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL;
|
|
LONG tmp;
|
|
|
|
switch (out->dwFlags & direction_flags)
|
|
{
|
|
case DIEFF_CARTESIAN:
|
|
out->rglDirection[0] = 10000;
|
|
for (i = 1; i <= in->cAxes; ++i)
|
|
{
|
|
tmp = cos( in->rglDirection[i - 1] * M_PI / 18000 ) * 10000;
|
|
for (j = 0; j < i; ++j)
|
|
out->rglDirection[j] = round( out->rglDirection[j] * tmp / 10000.0 );
|
|
out->rglDirection[i] = sin( in->rglDirection[i - 1] * M_PI / 18000 ) * 10000;
|
|
}
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
case DIEFF_POLAR:
|
|
out->rglDirection[0] = (in->rglDirection[0] + 9000) % 36000;
|
|
if (out->rglDirection[0] < 0) out->rglDirection[0] += 36000;
|
|
out->rglDirection[1] = 0;
|
|
out->cAxes = 2;
|
|
break;
|
|
case DIEFF_SPHERICAL:
|
|
for (i = 0; i < in->cAxes; ++i)
|
|
{
|
|
out->rglDirection[i] = in->rglDirection[i] % 36000;
|
|
if (out->rglDirection[i] < 0) out->rglDirection[i] += 36000;
|
|
}
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void convert_directions( const DIEFFECT *in, DIEFFECT *out )
|
|
{
|
|
DWORD direction_flags = DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL;
|
|
LONG directions[6] = {0};
|
|
DIEFFECT spherical = {.rglDirection = directions};
|
|
|
|
switch (in->dwFlags & direction_flags)
|
|
{
|
|
case DIEFF_CARTESIAN:
|
|
switch (out->dwFlags & direction_flags)
|
|
{
|
|
case DIEFF_CARTESIAN:
|
|
memcpy( out->rglDirection, in->rglDirection, in->cAxes * sizeof(LONG) );
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
case DIEFF_POLAR:
|
|
convert_directions_to_spherical( in, &spherical );
|
|
convert_directions_from_spherical( &spherical, out );
|
|
break;
|
|
case DIEFF_SPHERICAL:
|
|
convert_directions_to_spherical( in, out );
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DIEFF_POLAR:
|
|
switch (out->dwFlags & direction_flags)
|
|
{
|
|
case DIEFF_POLAR:
|
|
memcpy( out->rglDirection, in->rglDirection, in->cAxes * sizeof(LONG) );
|
|
out->cAxes = in->cAxes;
|
|
break;
|
|
case DIEFF_CARTESIAN:
|
|
convert_directions_to_spherical( in, &spherical );
|
|
convert_directions_from_spherical( &spherical, out );
|
|
break;
|
|
case DIEFF_SPHERICAL:
|
|
convert_directions_to_spherical( in, out );
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case DIEFF_SPHERICAL:
|
|
switch (out->dwFlags & direction_flags)
|
|
{
|
|
case DIEFF_POLAR:
|
|
case DIEFF_CARTESIAN:
|
|
convert_directions_from_spherical( in, out );
|
|
break;
|
|
case DIEFF_SPHERICAL:
|
|
convert_directions_to_spherical( in, out );
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_GetParameters( IDirectInputEffect *iface, DIEFFECT *params, DWORD flags )
|
|
{
|
|
DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(DIPROPHEADER),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_BYUSAGE,
|
|
};
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
ULONG i, count, capacity, object_flags, direction_flags;
|
|
BOOL ret;
|
|
|
|
TRACE( "iface %p, params %p, flags %#x.\n", iface, params, flags );
|
|
|
|
if (!params) return DI_OK;
|
|
if (params->dwSize != sizeof(DIEFFECT_DX6) && params->dwSize != sizeof(DIEFFECT_DX5)) return DIERR_INVALIDPARAM;
|
|
capacity = params->cAxes;
|
|
object_flags = params->dwFlags & (DIEFF_OBJECTIDS | DIEFF_OBJECTOFFSETS);
|
|
direction_flags = params->dwFlags & (DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL);
|
|
|
|
if (flags & DIEP_AXES)
|
|
{
|
|
if (!object_flags) return DIERR_INVALIDPARAM;
|
|
params->cAxes = impl->params.cAxes;
|
|
if (capacity < impl->params.cAxes) return DIERR_MOREDATA;
|
|
|
|
for (i = 0; i < impl->params.cAxes; ++i)
|
|
{
|
|
if (!params->rgdwAxes) return DIERR_INVALIDPARAM;
|
|
filter.dwObj = impl->params.rgdwAxes[i];
|
|
if (object_flags & DIEFF_OBJECTIDS)
|
|
ret = enum_objects( impl->joystick, &filter, DIDFT_AXIS, get_parameters_object_id,
|
|
¶ms->rgdwAxes[i] );
|
|
else
|
|
ret = enum_objects( impl->joystick, &filter, DIDFT_AXIS, get_parameters_object_ofs,
|
|
¶ms->rgdwAxes[i] );
|
|
if (ret != DIENUM_STOP) params->rgdwAxes[i] = 0;
|
|
}
|
|
}
|
|
|
|
if (flags & DIEP_DIRECTION)
|
|
{
|
|
if (!direction_flags) return DIERR_INVALIDPARAM;
|
|
|
|
count = params->cAxes = impl->params.cAxes;
|
|
if (!count) params->dwFlags &= ~(DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL);
|
|
if ((direction_flags & DIEFF_POLAR) && count != 2) return DIERR_INVALIDPARAM;
|
|
if (capacity < params->cAxes) return DIERR_MOREDATA;
|
|
|
|
if (!count) params->rglDirection = NULL;
|
|
else if (!params->rglDirection) return DIERR_INVALIDPARAM;
|
|
else convert_directions( &impl->params, params );
|
|
}
|
|
|
|
if (flags & DIEP_TYPESPECIFICPARAMS)
|
|
{
|
|
switch (impl->type)
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != sizeof(DIPERIODIC)) return DIERR_INVALIDPARAM;
|
|
memcpy( params->lpvTypeSpecificParams, &impl->periodic, sizeof(DIPERIODIC) );
|
|
break;
|
|
case PID_USAGE_ET_SPRING:
|
|
case PID_USAGE_ET_DAMPER:
|
|
case PID_USAGE_ET_INERTIA:
|
|
case PID_USAGE_ET_FRICTION:
|
|
count = impl->params.cbTypeSpecificParams;
|
|
capacity = params->cbTypeSpecificParams;
|
|
params->cbTypeSpecificParams = count;
|
|
if (capacity < count) return DIERR_MOREDATA;
|
|
memcpy( params->lpvTypeSpecificParams, impl->condition, params->cbTypeSpecificParams );
|
|
break;
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != sizeof(DICONSTANTFORCE)) return DIERR_INVALIDPARAM;
|
|
memcpy( params->lpvTypeSpecificParams, &impl->constant_force, sizeof(DICONSTANTFORCE) );
|
|
break;
|
|
case PID_USAGE_ET_RAMP:
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != sizeof(DIRAMPFORCE)) return DIERR_INVALIDPARAM;
|
|
memcpy( params->lpvTypeSpecificParams, &impl->constant_force, sizeof(DIRAMPFORCE) );
|
|
break;
|
|
case PID_USAGE_ET_CUSTOM_FORCE_DATA:
|
|
FIXME( "DIEP_TYPESPECIFICPARAMS not implemented!\n" );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
if (flags & DIEP_ENVELOPE)
|
|
{
|
|
if (!params->lpEnvelope) return E_POINTER;
|
|
if (params->lpEnvelope->dwSize != sizeof(DIENVELOPE)) return DIERR_INVALIDPARAM;
|
|
memcpy( params->lpEnvelope, &impl->envelope, sizeof(DIENVELOPE) );
|
|
}
|
|
|
|
if (flags & DIEP_DURATION) params->dwDuration = impl->params.dwDuration;
|
|
if (flags & DIEP_GAIN) params->dwGain = impl->params.dwGain;
|
|
if (flags & DIEP_SAMPLEPERIOD) params->dwSamplePeriod = impl->params.dwSamplePeriod;
|
|
if (flags & DIEP_STARTDELAY)
|
|
{
|
|
if (params->dwSize != sizeof(DIEFFECT_DX6)) return DIERR_INVALIDPARAM;
|
|
params->dwStartDelay = impl->params.dwStartDelay;
|
|
}
|
|
if (flags & DIEP_TRIGGERREPEATINTERVAL) params->dwTriggerRepeatInterval = impl->params.dwTriggerRepeatInterval;
|
|
|
|
if (flags & DIEP_TRIGGERBUTTON)
|
|
{
|
|
if (!object_flags) return DIERR_INVALIDPARAM;
|
|
|
|
filter.dwObj = impl->params.dwTriggerButton;
|
|
if (object_flags & DIEFF_OBJECTIDS)
|
|
ret = enum_objects( impl->joystick, &filter, DIDFT_BUTTON, get_parameters_object_id,
|
|
¶ms->dwTriggerButton );
|
|
else
|
|
ret = enum_objects( impl->joystick, &filter, DIDFT_BUTTON, get_parameters_object_ofs,
|
|
¶ms->dwTriggerButton );
|
|
if (ret != DIENUM_STOP) params->dwTriggerButton = -1;
|
|
}
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL set_parameters_object( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DWORD usages = MAKELONG( instance->wUsage, instance->wUsagePage );
|
|
*(DWORD *)data = usages;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_SetParameters( IDirectInputEffect *iface,
|
|
const DIEFFECT *params, DWORD flags )
|
|
{
|
|
DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(DIPROPHEADER),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_BYUSAGE,
|
|
};
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
ULONG i, count, old_value, object_flags, direction_flags;
|
|
HRESULT hr;
|
|
BOOL ret;
|
|
|
|
TRACE( "iface %p, params %p, flags %#x.\n", iface, params, flags );
|
|
|
|
if (!params) return E_POINTER;
|
|
if (params->dwSize != sizeof(DIEFFECT_DX6) && params->dwSize != sizeof(DIEFFECT_DX5)) return DIERR_INVALIDPARAM;
|
|
object_flags = params->dwFlags & (DIEFF_OBJECTIDS | DIEFF_OBJECTOFFSETS);
|
|
direction_flags = params->dwFlags & (DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL);
|
|
|
|
if (object_flags & DIEFF_OBJECTIDS) filter.dwHow = DIPH_BYID;
|
|
else filter.dwHow = DIPH_BYOFFSET;
|
|
|
|
if (flags & DIEP_AXES)
|
|
{
|
|
if (!object_flags) return DIERR_INVALIDPARAM;
|
|
if (!params->rgdwAxes) return DIERR_INVALIDPARAM;
|
|
if (impl->params.cAxes) return DIERR_ALREADYINITIALIZED;
|
|
count = impl->joystick->pid_effect_update.axis_count;
|
|
if (params->cAxes > count) return DIERR_INVALIDPARAM;
|
|
|
|
impl->params.cAxes = params->cAxes;
|
|
for (i = 0; i < params->cAxes; ++i)
|
|
{
|
|
filter.dwObj = params->rgdwAxes[i];
|
|
ret = enum_objects( impl->joystick, &filter, DIDFT_AXIS, set_parameters_object,
|
|
&impl->params.rgdwAxes[i] );
|
|
if (ret != DIENUM_STOP) impl->params.rgdwAxes[i] = 0;
|
|
}
|
|
|
|
impl->modified |= DIEP_AXES;
|
|
}
|
|
|
|
if (flags & DIEP_DIRECTION)
|
|
{
|
|
if (!direction_flags) return DIERR_INVALIDPARAM;
|
|
if (!params->rglDirection) return DIERR_INVALIDPARAM;
|
|
|
|
count = impl->params.cAxes;
|
|
if (params->cAxes < count) return DIERR_INVALIDPARAM;
|
|
if ((direction_flags & DIEFF_POLAR) && count != 2) return DIERR_INVALIDPARAM;
|
|
if ((direction_flags & DIEFF_CARTESIAN) && params->cAxes != count) return DIERR_INVALIDPARAM;
|
|
|
|
impl->params.dwFlags &= ~(DIEFF_CARTESIAN | DIEFF_POLAR | DIEFF_SPHERICAL);
|
|
impl->params.dwFlags |= direction_flags;
|
|
if (memcmp( impl->params.rglDirection, params->rglDirection, count * sizeof(LONG) ))
|
|
impl->modified |= DIEP_DIRECTION;
|
|
memcpy( impl->params.rglDirection, params->rglDirection, count * sizeof(LONG) );
|
|
}
|
|
|
|
if (flags & DIEP_TYPESPECIFICPARAMS)
|
|
{
|
|
switch (impl->type)
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != sizeof(DIPERIODIC)) return DIERR_INVALIDPARAM;
|
|
if (memcmp( &impl->periodic, params->lpvTypeSpecificParams, sizeof(DIPERIODIC) ))
|
|
impl->modified |= DIEP_TYPESPECIFICPARAMS;
|
|
memcpy( &impl->periodic, params->lpvTypeSpecificParams, sizeof(DIPERIODIC) );
|
|
impl->params.cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
break;
|
|
case PID_USAGE_ET_SPRING:
|
|
case PID_USAGE_ET_DAMPER:
|
|
case PID_USAGE_ET_INERTIA:
|
|
case PID_USAGE_ET_FRICTION:
|
|
if ((count = impl->params.cAxes))
|
|
{
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != count * sizeof(DICONDITION) &&
|
|
params->cbTypeSpecificParams != sizeof(DICONDITION))
|
|
return DIERR_INVALIDPARAM;
|
|
if (memcmp( impl->condition, params->lpvTypeSpecificParams, params->cbTypeSpecificParams ))
|
|
impl->modified |= DIEP_TYPESPECIFICPARAMS;
|
|
memcpy( impl->condition, params->lpvTypeSpecificParams, params->cbTypeSpecificParams );
|
|
impl->params.cbTypeSpecificParams = params->cbTypeSpecificParams;
|
|
}
|
|
break;
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != sizeof(DICONSTANTFORCE)) return DIERR_INVALIDPARAM;
|
|
if (memcmp( &impl->constant_force, params->lpvTypeSpecificParams, sizeof(DICONSTANTFORCE) ))
|
|
impl->modified |= DIEP_TYPESPECIFICPARAMS;
|
|
memcpy( &impl->constant_force, params->lpvTypeSpecificParams, sizeof(DICONSTANTFORCE) );
|
|
impl->params.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
|
break;
|
|
case PID_USAGE_ET_RAMP:
|
|
if (!params->lpvTypeSpecificParams) return E_POINTER;
|
|
if (params->cbTypeSpecificParams != sizeof(DIRAMPFORCE)) return DIERR_INVALIDPARAM;
|
|
if (memcmp( &impl->constant_force, params->lpvTypeSpecificParams, sizeof(DIRAMPFORCE) ))
|
|
impl->modified |= DIEP_TYPESPECIFICPARAMS;
|
|
memcpy( &impl->constant_force, params->lpvTypeSpecificParams, sizeof(DIRAMPFORCE) );
|
|
impl->params.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
|
|
break;
|
|
case PID_USAGE_ET_CUSTOM_FORCE_DATA:
|
|
FIXME( "DIEP_TYPESPECIFICPARAMS not implemented!\n" );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
if ((flags & DIEP_ENVELOPE) && params->lpEnvelope)
|
|
{
|
|
if (params->lpEnvelope->dwSize != sizeof(DIENVELOPE)) return DIERR_INVALIDPARAM;
|
|
if (memcmp( &impl->envelope, params->lpEnvelope, sizeof(DIENVELOPE) ))
|
|
impl->modified |= DIEP_ENVELOPE;
|
|
memcpy( &impl->envelope, params->lpEnvelope, sizeof(DIENVELOPE) );
|
|
}
|
|
|
|
if (flags & DIEP_DURATION)
|
|
{
|
|
impl->modified |= DIEP_DURATION;
|
|
impl->params.dwDuration = params->dwDuration;
|
|
}
|
|
if (flags & DIEP_GAIN)
|
|
{
|
|
if (impl->params.dwGain != params->dwGain) impl->modified |= DIEP_GAIN;
|
|
impl->params.dwGain = params->dwGain;
|
|
}
|
|
if (flags & DIEP_SAMPLEPERIOD)
|
|
{
|
|
if (impl->params.dwSamplePeriod != params->dwSamplePeriod) impl->modified |= DIEP_SAMPLEPERIOD;
|
|
impl->params.dwSamplePeriod = params->dwSamplePeriod;
|
|
}
|
|
if (flags & DIEP_STARTDELAY)
|
|
{
|
|
if (params->dwSize != sizeof(DIEFFECT_DX6)) return DIERR_INVALIDPARAM;
|
|
if (impl->params.dwStartDelay != params->dwStartDelay) impl->modified |= DIEP_STARTDELAY;
|
|
impl->params.dwStartDelay = params->dwStartDelay;
|
|
}
|
|
if (flags & DIEP_TRIGGERREPEATINTERVAL)
|
|
{
|
|
if (impl->params.dwTriggerRepeatInterval != params->dwTriggerRepeatInterval)
|
|
impl->modified |= DIEP_TRIGGERREPEATINTERVAL;
|
|
impl->params.dwTriggerRepeatInterval = params->dwTriggerRepeatInterval;
|
|
}
|
|
|
|
if (flags & DIEP_TRIGGERBUTTON)
|
|
{
|
|
if (!object_flags) return DIERR_INVALIDPARAM;
|
|
|
|
filter.dwObj = params->dwTriggerButton;
|
|
old_value = impl->params.dwTriggerButton;
|
|
ret = enum_objects( impl->joystick, &filter, DIDFT_BUTTON, set_parameters_object,
|
|
&impl->params.dwTriggerButton );
|
|
if (ret != DIENUM_STOP) impl->params.dwTriggerButton = -1;
|
|
if (impl->params.dwTriggerButton != old_value) impl->modified |= DIEP_TRIGGERBUTTON;
|
|
}
|
|
|
|
impl->flags |= flags;
|
|
|
|
if (flags & DIEP_NODOWNLOAD) return DI_DOWNLOADSKIPPED;
|
|
if (flags & DIEP_START) hr = IDirectInputEffect_Start( iface, 1, 0 );
|
|
else hr = IDirectInputEffect_Download( iface );
|
|
if (hr == DIERR_NOTEXCLUSIVEACQUIRED) return DI_DOWNLOADSKIPPED;
|
|
if (FAILED(hr)) return hr;
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Start( IDirectInputEffect *iface, DWORD iterations, DWORD flags )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
struct pid_control_report *effect_control = &impl->joystick->pid_effect_control;
|
|
ULONG count, report_len = impl->joystick->caps.OutputReportByteLength;
|
|
PHIDP_PREPARSED_DATA preparsed = impl->joystick->preparsed;
|
|
HANDLE device = impl->joystick->device;
|
|
NTSTATUS status;
|
|
USAGE control;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, iterations %u, flags %#x.\n", iface, iterations, flags );
|
|
|
|
if ((flags & ~(DIES_NODOWNLOAD|DIES_SOLO))) return DIERR_INVALIDPARAM;
|
|
if (flags & DIES_SOLO) control = PID_USAGE_OP_EFFECT_START_SOLO;
|
|
else control = PID_USAGE_OP_EFFECT_START;
|
|
|
|
EnterCriticalSection( &impl->joystick->base.crit );
|
|
if (!impl->joystick->base.acquired || !(impl->joystick->base.dwCoopLevel & DISCL_EXCLUSIVE))
|
|
hr = DIERR_NOTEXCLUSIVEACQUIRED;
|
|
else if ((flags & DIES_NODOWNLOAD) && !impl->index)
|
|
hr = DIERR_NOTDOWNLOADED;
|
|
else if ((flags & DIES_NODOWNLOAD) || SUCCEEDED(hr = IDirectInputEffect_Download( iface )))
|
|
{
|
|
count = 1;
|
|
status = HidP_InitializeReportForID( HidP_Output, effect_control->id, preparsed,
|
|
impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
impl->index, preparsed, impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsages( HidP_Output, HID_USAGE_PAGE_PID, effect_control->control_coll,
|
|
&control, &count, preparsed, impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_LOOP_COUNT,
|
|
iterations, preparsed, impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else if (WriteFile( device, impl->effect_control_buf, report_len, NULL, NULL )) hr = DI_OK;
|
|
else hr = DIERR_INPUTLOST;
|
|
}
|
|
LeaveCriticalSection( &impl->joystick->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Stop( IDirectInputEffect *iface )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
struct pid_control_report *effect_control = &impl->joystick->pid_effect_control;
|
|
ULONG count, report_len = impl->joystick->caps.OutputReportByteLength;
|
|
PHIDP_PREPARSED_DATA preparsed = impl->joystick->preparsed;
|
|
HANDLE device = impl->joystick->device;
|
|
NTSTATUS status;
|
|
USAGE control;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
EnterCriticalSection( &impl->joystick->base.crit );
|
|
if (!impl->joystick->base.acquired || !(impl->joystick->base.dwCoopLevel & DISCL_EXCLUSIVE))
|
|
hr = DIERR_NOTEXCLUSIVEACQUIRED;
|
|
else if (!impl->index)
|
|
hr = DIERR_NOTDOWNLOADED;
|
|
else
|
|
{
|
|
count = 1;
|
|
control = PID_USAGE_OP_EFFECT_STOP;
|
|
status = HidP_InitializeReportForID( HidP_Output, effect_control->id, preparsed,
|
|
impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
impl->index, preparsed, impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsages( HidP_Output, HID_USAGE_PAGE_PID, effect_control->control_coll,
|
|
&control, &count, preparsed, impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_LOOP_COUNT,
|
|
0, preparsed, impl->effect_control_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else if (WriteFile( device, impl->effect_control_buf, report_len, NULL, NULL )) hr = DI_OK;
|
|
else hr = DIERR_INPUTLOST;
|
|
}
|
|
LeaveCriticalSection( &impl->joystick->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_GetEffectStatus( IDirectInputEffect *iface, DWORD *status )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
HRESULT hr;
|
|
|
|
FIXME( "iface %p, status %p semi-stub!\n", iface, status );
|
|
|
|
if (!status) return E_POINTER;
|
|
*status = 0;
|
|
|
|
EnterCriticalSection( &impl->joystick->base.crit );
|
|
if (!impl->joystick->base.acquired || !(impl->joystick->base.dwCoopLevel & DISCL_EXCLUSIVE))
|
|
hr = DIERR_NOTEXCLUSIVEACQUIRED;
|
|
else if (!impl->index)
|
|
hr = DIERR_NOTDOWNLOADED;
|
|
else
|
|
hr = DI_OK;
|
|
LeaveCriticalSection( &impl->joystick->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static void set_parameter_value( struct hid_joystick_effect *impl, char *report_buf,
|
|
struct hid_value_caps *caps, LONG value )
|
|
{
|
|
return set_report_value( impl->joystick, report_buf, caps, value );
|
|
}
|
|
|
|
static void set_parameter_value_angle( struct hid_joystick_effect *impl, char *report_buf,
|
|
struct hid_value_caps *caps, LONG value )
|
|
{
|
|
LONG exp;
|
|
if (!caps) return;
|
|
exp = caps->units_exp;
|
|
if (caps->units != 0x14) WARN( "unknown angle unit caps %x\n", caps->units );
|
|
else if (exp < -2) while (exp++ < -2) value *= 10;
|
|
else if (exp > -2) while (exp-- > -2) value /= 10;
|
|
set_parameter_value( impl, report_buf, caps, value );
|
|
}
|
|
|
|
static void set_parameter_value_us( struct hid_joystick_effect *impl, char *report_buf,
|
|
struct hid_value_caps *caps, LONG value )
|
|
{
|
|
LONG exp;
|
|
if (!caps) return;
|
|
exp = caps->units_exp;
|
|
if (value == INFINITE) value = caps->physical_min - 1;
|
|
else if (caps->units != 0x1003) WARN( "unknown time unit caps %x\n", caps->units );
|
|
else if (exp < -6) while (exp++ < -6) value *= 10;
|
|
else if (exp > -6) while (exp-- > -6) value /= 10;
|
|
set_parameter_value( impl, report_buf, caps, value );
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Download( IDirectInputEffect *iface )
|
|
{
|
|
static const DWORD complete_mask = DIEP_AXES | DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS;
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
struct pid_set_constant_force *set_constant_force = &impl->joystick->pid_set_constant_force;
|
|
struct pid_set_ramp_force *set_ramp_force = &impl->joystick->pid_set_ramp_force;
|
|
struct pid_effect_update *effect_update = &impl->joystick->pid_effect_update;
|
|
struct pid_set_condition *set_condition = &impl->joystick->pid_set_condition;
|
|
struct pid_set_periodic *set_periodic = &impl->joystick->pid_set_periodic;
|
|
struct pid_set_envelope *set_envelope = &impl->joystick->pid_set_envelope;
|
|
ULONG report_len = impl->joystick->caps.OutputReportByteLength;
|
|
HANDLE device = impl->joystick->device;
|
|
struct hid_value_caps *caps;
|
|
LONG directions[4] = {0};
|
|
DWORD i, tmp, count;
|
|
DIEFFECT spherical;
|
|
NTSTATUS status;
|
|
USAGE usage;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p\n", iface );
|
|
|
|
EnterCriticalSection( &impl->joystick->base.crit );
|
|
if (impl->modified) hr = DI_OK;
|
|
else hr = DI_NOEFFECT;
|
|
|
|
if (!impl->joystick->base.acquired || !(impl->joystick->base.dwCoopLevel & DISCL_EXCLUSIVE))
|
|
hr = DIERR_NOTEXCLUSIVEACQUIRED;
|
|
else if ((impl->flags & complete_mask) != complete_mask)
|
|
hr = DIERR_INCOMPLETEEFFECT;
|
|
else if (!impl->index && SUCCEEDED(hr = find_next_effect_id( impl->joystick, &impl->index, impl->type )))
|
|
{
|
|
if (!impl->type_specific_buf[0]) status = HIDP_STATUS_SUCCESS;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
impl->index, impl->joystick->preparsed, impl->type_specific_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue returned %#x\n", status );
|
|
|
|
if (!impl->set_envelope_buf[0]) status = HIDP_STATUS_SUCCESS;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
impl->index, impl->joystick->preparsed, impl->set_envelope_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue returned %#x\n", status );
|
|
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
impl->index, impl->joystick->preparsed, impl->effect_update_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else hr = DI_OK;
|
|
}
|
|
|
|
if (hr == DI_OK)
|
|
{
|
|
switch (impl->type)
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
if (!(impl->modified & DIEP_TYPESPECIFICPARAMS)) break;
|
|
|
|
set_parameter_value( impl, impl->type_specific_buf, set_periodic->magnitude_caps,
|
|
impl->periodic.dwMagnitude );
|
|
set_parameter_value_us( impl, impl->type_specific_buf, set_periodic->period_caps,
|
|
impl->periodic.dwPeriod );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_periodic->phase_caps,
|
|
impl->periodic.dwPhase );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_periodic->offset_caps,
|
|
impl->periodic.lOffset );
|
|
|
|
if (!WriteFile( device, impl->type_specific_buf, report_len, NULL, NULL )) hr = DIERR_INPUTLOST;
|
|
else impl->modified &= ~DIEP_TYPESPECIFICPARAMS;
|
|
break;
|
|
case PID_USAGE_ET_SPRING:
|
|
case PID_USAGE_ET_DAMPER:
|
|
case PID_USAGE_ET_INERTIA:
|
|
case PID_USAGE_ET_FRICTION:
|
|
if (!(impl->modified & DIEP_TYPESPECIFICPARAMS)) break;
|
|
|
|
for (i = 0; i < impl->params.cbTypeSpecificParams / sizeof(DICONDITION); ++i)
|
|
{
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_PARAMETER_BLOCK_OFFSET,
|
|
i, impl->joystick->preparsed, impl->type_specific_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue %04x:%04x returned %#x\n",
|
|
HID_USAGE_PAGE_PID, PID_USAGE_PARAMETER_BLOCK_OFFSET, status );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_condition->center_point_offset_caps,
|
|
impl->condition[i].lOffset );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_condition->positive_coefficient_caps,
|
|
impl->condition[i].lPositiveCoefficient );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_condition->negative_coefficient_caps,
|
|
impl->condition[i].lNegativeCoefficient );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_condition->positive_saturation_caps,
|
|
impl->condition[i].dwPositiveSaturation );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_condition->negative_saturation_caps,
|
|
impl->condition[i].dwNegativeSaturation );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_condition->dead_band_caps,
|
|
impl->condition[i].lDeadBand );
|
|
|
|
if (!WriteFile( device, impl->type_specific_buf, report_len, NULL, NULL )) hr = DIERR_INPUTLOST;
|
|
else impl->modified &= ~DIEP_TYPESPECIFICPARAMS;
|
|
}
|
|
break;
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
if (!(impl->modified & DIEP_TYPESPECIFICPARAMS)) break;
|
|
|
|
set_parameter_value( impl, impl->type_specific_buf, set_constant_force->magnitude_caps,
|
|
impl->constant_force.lMagnitude );
|
|
|
|
if (!WriteFile( device, impl->type_specific_buf, report_len, NULL, NULL )) hr = DIERR_INPUTLOST;
|
|
else impl->modified &= ~DIEP_TYPESPECIFICPARAMS;
|
|
break;
|
|
case PID_USAGE_ET_RAMP:
|
|
if (!(impl->modified & DIEP_TYPESPECIFICPARAMS)) break;
|
|
|
|
set_parameter_value( impl, impl->type_specific_buf, set_ramp_force->start_caps,
|
|
impl->ramp_force.lStart );
|
|
set_parameter_value( impl, impl->type_specific_buf, set_ramp_force->end_caps,
|
|
impl->ramp_force.lEnd );
|
|
|
|
if (!WriteFile( device, impl->type_specific_buf, report_len, NULL, NULL )) hr = DIERR_INPUTLOST;
|
|
else impl->modified &= ~DIEP_TYPESPECIFICPARAMS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hr == DI_OK)
|
|
{
|
|
switch (impl->type)
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
case PID_USAGE_ET_RAMP:
|
|
if (!(impl->modified & DIEP_ENVELOPE)) break;
|
|
|
|
set_parameter_value( impl, impl->set_envelope_buf, set_envelope->attack_level_caps,
|
|
impl->envelope.dwAttackLevel );
|
|
set_parameter_value_us( impl, impl->set_envelope_buf, set_envelope->attack_time_caps,
|
|
impl->envelope.dwAttackTime );
|
|
set_parameter_value( impl, impl->set_envelope_buf, set_envelope->fade_level_caps,
|
|
impl->envelope.dwFadeLevel );
|
|
set_parameter_value_us( impl, impl->set_envelope_buf, set_envelope->fade_time_caps,
|
|
impl->envelope.dwFadeTime );
|
|
|
|
if (!WriteFile( device, impl->set_envelope_buf, report_len, NULL, NULL )) hr = DIERR_INPUTLOST;
|
|
else impl->modified &= ~DIEP_ENVELOPE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hr == DI_OK && impl->modified)
|
|
{
|
|
set_parameter_value_us( impl, impl->effect_update_buf, effect_update->duration_caps,
|
|
impl->params.dwDuration );
|
|
set_parameter_value( impl, impl->effect_update_buf, effect_update->gain_caps,
|
|
impl->params.dwGain );
|
|
set_parameter_value_us( impl, impl->effect_update_buf, effect_update->sample_period_caps,
|
|
impl->params.dwSamplePeriod );
|
|
set_parameter_value_us( impl, impl->effect_update_buf, effect_update->start_delay_caps,
|
|
impl->params.dwStartDelay );
|
|
set_parameter_value_us( impl, impl->effect_update_buf, effect_update->trigger_repeat_interval_caps,
|
|
impl->params.dwTriggerRepeatInterval );
|
|
|
|
count = 1;
|
|
usage = PID_USAGE_DIRECTION_ENABLE;
|
|
status = HidP_SetUsages( HidP_Output, HID_USAGE_PAGE_PID, 0, &usage, &count,
|
|
impl->joystick->preparsed, impl->effect_update_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsages returned %#x\n", status );
|
|
|
|
spherical.rglDirection = directions;
|
|
convert_directions_to_spherical( &impl->params, &spherical );
|
|
|
|
for (i = 0; i < min( effect_update->direction_count, spherical.cAxes ); ++i)
|
|
{
|
|
tmp = directions[i] + (i == 0 ? 9000 : 0);
|
|
caps = effect_update->direction_caps[effect_update->direction_count - i - 1];
|
|
set_parameter_value_angle( impl, impl->effect_update_buf, caps, tmp % 36000 );
|
|
}
|
|
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_TRIGGER_BUTTON,
|
|
impl->params.dwTriggerButton, impl->joystick->preparsed,
|
|
impl->effect_update_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue returned %#x\n", status );
|
|
|
|
if (!WriteFile( device, impl->effect_update_buf, report_len, NULL, NULL )) hr = DIERR_INPUTLOST;
|
|
else impl->modified = 0;
|
|
}
|
|
LeaveCriticalSection( &impl->joystick->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Unload( IDirectInputEffect *iface )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
struct hid_joystick *joystick = impl->joystick;
|
|
struct pid_device_pool *device_pool = &joystick->pid_device_pool;
|
|
struct pid_block_free *block_free = &joystick->pid_block_free;
|
|
ULONG report_len = joystick->caps.OutputReportByteLength;
|
|
HRESULT hr = DI_OK;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "iface %p\n", iface );
|
|
|
|
EnterCriticalSection( &joystick->base.crit );
|
|
if (!impl->index)
|
|
hr = DI_NOEFFECT;
|
|
else if (SUCCEEDED(hr = IDirectInputEffect_Stop( iface )))
|
|
{
|
|
if (!device_pool->device_managed_caps)
|
|
joystick->effect_inuse[impl->index - 1] = FALSE;
|
|
else if (block_free->id)
|
|
{
|
|
status = HidP_InitializeReportForID( HidP_Output, block_free->id, joystick->preparsed,
|
|
joystick->output_report_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX,
|
|
impl->index, joystick->preparsed, joystick->output_report_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else if (WriteFile( joystick->device, joystick->output_report_buf, report_len, NULL, NULL )) hr = DI_OK;
|
|
else hr = DIERR_INPUTLOST;
|
|
}
|
|
|
|
impl->modified = impl->flags;
|
|
impl->index = 0;
|
|
}
|
|
LeaveCriticalSection( &joystick->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Escape( IDirectInputEffect *iface, DIEFFESCAPE *escape )
|
|
{
|
|
FIXME( "iface %p, escape %p stub!\n", iface, escape );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static IDirectInputEffectVtbl hid_joystick_effect_vtbl =
|
|
{
|
|
/*** IUnknown methods ***/
|
|
hid_joystick_effect_QueryInterface,
|
|
hid_joystick_effect_AddRef,
|
|
hid_joystick_effect_Release,
|
|
/*** IDirectInputEffect methods ***/
|
|
hid_joystick_effect_Initialize,
|
|
hid_joystick_effect_GetEffectGuid,
|
|
hid_joystick_effect_GetParameters,
|
|
hid_joystick_effect_SetParameters,
|
|
hid_joystick_effect_Start,
|
|
hid_joystick_effect_Stop,
|
|
hid_joystick_effect_GetEffectStatus,
|
|
hid_joystick_effect_Download,
|
|
hid_joystick_effect_Unload,
|
|
hid_joystick_effect_Escape,
|
|
};
|
|
|
|
static HRESULT hid_joystick_create_effect( IDirectInputDevice8W *iface, IDirectInputEffect **out )
|
|
{
|
|
struct hid_joystick *joystick = impl_from_IDirectInputDevice8W( iface );
|
|
struct hid_joystick_effect *impl;
|
|
ULONG report_len;
|
|
|
|
if (!(impl = calloc( 1, sizeof(*impl) ))) return DIERR_OUTOFMEMORY;
|
|
impl->IDirectInputEffect_iface.lpVtbl = &hid_joystick_effect_vtbl;
|
|
impl->ref = 1;
|
|
impl->joystick = joystick;
|
|
hid_joystick_addref( &joystick->base.IDirectInputDevice8W_iface );
|
|
|
|
EnterCriticalSection( &joystick->base.crit );
|
|
list_add_tail( &joystick->effect_list, &impl->entry );
|
|
LeaveCriticalSection( &joystick->base.crit );
|
|
|
|
report_len = joystick->caps.OutputReportByteLength;
|
|
if (!(impl->effect_control_buf = malloc( report_len ))) goto failed;
|
|
if (!(impl->effect_update_buf = malloc( report_len ))) goto failed;
|
|
if (!(impl->type_specific_buf = malloc( report_len ))) goto failed;
|
|
if (!(impl->set_envelope_buf = malloc( report_len ))) goto failed;
|
|
|
|
impl->envelope.dwSize = sizeof(DIENVELOPE);
|
|
impl->params.dwSize = sizeof(DIEFFECT);
|
|
impl->params.lpEnvelope = &impl->envelope;
|
|
impl->params.rgdwAxes = impl->axes;
|
|
impl->params.rglDirection = impl->directions;
|
|
impl->params.dwTriggerButton = -1;
|
|
|
|
*out = &impl->IDirectInputEffect_iface;
|
|
return DI_OK;
|
|
|
|
failed:
|
|
IDirectInputEffect_Release( &impl->IDirectInputEffect_iface );
|
|
return DIERR_OUTOFMEMORY;
|
|
}
|