2022-11-27 21:03:06 +00:00
|
|
|
/*
|
|
|
|
* Copyright 2022 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 <stdarg.h>
|
|
|
|
#include <stddef.h>
|
2022-11-27 22:35:23 +00:00
|
|
|
#include <math.h>
|
2022-11-27 21:03:06 +00:00
|
|
|
|
|
|
|
#include "windef.h"
|
|
|
|
#include "winbase.h"
|
|
|
|
#include "winuser.h"
|
|
|
|
#include "wingdi.h"
|
|
|
|
|
|
|
|
#include "dinput.h"
|
|
|
|
|
|
|
|
#include "wine/debug.h"
|
|
|
|
#include "wine/list.h"
|
|
|
|
|
|
|
|
#include "joy_private.h"
|
|
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(joycpl);
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
struct effect
|
|
|
|
{
|
|
|
|
struct list entry;
|
|
|
|
IDirectInputEffect *effect;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct device
|
|
|
|
{
|
|
|
|
struct list entry;
|
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
};
|
|
|
|
|
2022-11-27 21:03:06 +00:00
|
|
|
static CRITICAL_SECTION state_cs;
|
|
|
|
static CRITICAL_SECTION_DEBUG state_cs_debug =
|
|
|
|
{
|
|
|
|
0, 0, &state_cs,
|
|
|
|
{ &state_cs_debug.ProcessLocksList, &state_cs_debug.ProcessLocksList },
|
|
|
|
0, 0, { (DWORD_PTR)(__FILE__ ": state_cs") }
|
|
|
|
};
|
|
|
|
static CRITICAL_SECTION state_cs = { &state_cs_debug, -1, 0, 0, 0, 0 };
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
static struct list effects = LIST_INIT( effects );
|
|
|
|
static IDirectInputEffect *effect_selected;
|
2022-11-27 21:03:06 +00:00
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
static struct list devices = LIST_INIT( devices );
|
|
|
|
static IDirectInputDevice8W *device_selected;
|
|
|
|
|
|
|
|
static HWND dialog_hwnd;
|
|
|
|
static HANDLE state_event;
|
|
|
|
|
|
|
|
static BOOL CALLBACK enum_effects( const DIEFFECTINFOW *info, void *context )
|
2022-11-27 21:03:06 +00:00
|
|
|
{
|
2022-11-29 20:20:41 +00:00
|
|
|
IDirectInputDevice8W *device = context;
|
|
|
|
DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
|
|
|
|
LONG direction[2] = {0};
|
|
|
|
DIEFFECT params =
|
|
|
|
{
|
|
|
|
.dwSize = sizeof(DIEFFECT),
|
|
|
|
.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS,
|
|
|
|
.dwDuration = 2 * DI_SECONDS,
|
|
|
|
.dwGain = DI_FFNOMINALMAX,
|
|
|
|
.rglDirection = direction,
|
|
|
|
.rgdwAxes = axes,
|
|
|
|
.cAxes = 2,
|
|
|
|
};
|
|
|
|
DICONSTANTFORCE constant =
|
|
|
|
{
|
|
|
|
.lMagnitude = DI_FFNOMINALMAX,
|
|
|
|
};
|
|
|
|
DIPERIODIC periodic =
|
|
|
|
{
|
|
|
|
.dwMagnitude = DI_FFNOMINALMAX,
|
|
|
|
.dwPeriod = DI_SECONDS / 2,
|
|
|
|
};
|
|
|
|
DICONDITION condition =
|
|
|
|
{
|
|
|
|
.dwPositiveSaturation = 10000,
|
|
|
|
.dwNegativeSaturation = 10000,
|
|
|
|
.lPositiveCoefficient = 10000,
|
|
|
|
.lNegativeCoefficient = 10000,
|
|
|
|
};
|
|
|
|
DIRAMPFORCE ramp =
|
|
|
|
{
|
|
|
|
.lEnd = DI_FFNOMINALMAX,
|
|
|
|
};
|
|
|
|
IDirectInputEffect *effect;
|
|
|
|
struct effect *entry;
|
|
|
|
HRESULT hr;
|
|
|
|
|
|
|
|
hr = IDirectInputDevice8_Acquire( device );
|
|
|
|
if (FAILED(hr)) return DIENUM_CONTINUE;
|
|
|
|
|
|
|
|
if (!(entry = calloc( 1, sizeof(*entry) ))) return DIENUM_STOP;
|
|
|
|
|
|
|
|
if (IsEqualGUID( &info->guid, &GUID_RampForce ))
|
|
|
|
{
|
|
|
|
params.cbTypeSpecificParams = sizeof(ramp);
|
|
|
|
params.lpvTypeSpecificParams = &ramp;
|
|
|
|
params.dwFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
|
|
}
|
|
|
|
else if (IsEqualGUID( &info->guid, &GUID_ConstantForce ))
|
|
|
|
{
|
|
|
|
params.cbTypeSpecificParams = sizeof(constant);
|
|
|
|
params.lpvTypeSpecificParams = &constant;
|
|
|
|
params.dwFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
|
|
}
|
|
|
|
else if (IsEqualGUID( &info->guid, &GUID_Sine ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_Square ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_Triangle ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_SawtoothUp ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_SawtoothDown ))
|
|
|
|
{
|
|
|
|
params.cbTypeSpecificParams = sizeof(periodic);
|
|
|
|
params.lpvTypeSpecificParams = &periodic;
|
|
|
|
params.dwFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
|
|
}
|
|
|
|
else if (IsEqualGUID( &info->guid, &GUID_Spring ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_Damper ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_Inertia ) ||
|
|
|
|
IsEqualGUID( &info->guid, &GUID_Friction ))
|
|
|
|
{
|
|
|
|
params.cbTypeSpecificParams = sizeof(condition);
|
|
|
|
params.lpvTypeSpecificParams = &condition;
|
|
|
|
params.dwFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
|
|
}
|
|
|
|
|
|
|
|
do hr = IDirectInputDevice2_CreateEffect( device, &info->guid, ¶ms, &effect, NULL );
|
|
|
|
while (FAILED(hr) && --params.cAxes);
|
|
|
|
|
|
|
|
if (FAILED(hr))
|
|
|
|
{
|
|
|
|
FIXME( "Failed to create effect with type %s, hr %#lx\n", debugstr_guid( &info->guid ), hr );
|
|
|
|
free( entry );
|
|
|
|
return DIENUM_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
entry->effect = effect;
|
|
|
|
list_add_tail( &effects, &entry->entry );
|
|
|
|
|
|
|
|
return DIENUM_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_selected_effect( IDirectInputEffect *effect )
|
|
|
|
{
|
|
|
|
IDirectInputEffect *previous;
|
2022-11-27 21:03:06 +00:00
|
|
|
|
|
|
|
EnterCriticalSection( &state_cs );
|
2022-11-29 20:20:41 +00:00
|
|
|
if ((previous = effect_selected)) IDirectInputEffect_Release( previous );
|
|
|
|
if ((effect_selected = effect)) IDirectInput_AddRef( effect );
|
2022-11-27 21:03:06 +00:00
|
|
|
LeaveCriticalSection( &state_cs );
|
2022-11-29 20:20:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static IDirectInputEffect *get_selected_effect(void)
|
|
|
|
{
|
|
|
|
IDirectInputEffect *effect;
|
|
|
|
|
|
|
|
EnterCriticalSection( &state_cs );
|
|
|
|
if ((effect = effect_selected)) IDirectInputEffect_AddRef( effect );
|
|
|
|
LeaveCriticalSection( &state_cs );
|
|
|
|
|
|
|
|
return effect;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void clear_effects(void)
|
|
|
|
{
|
|
|
|
struct effect *effect, *next;
|
2022-11-27 21:03:06 +00:00
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
set_selected_effect( NULL );
|
|
|
|
|
|
|
|
LIST_FOR_EACH_ENTRY_SAFE( effect, next, &effects, struct effect, entry )
|
|
|
|
{
|
|
|
|
list_remove( &effect->entry );
|
|
|
|
IDirectInputEffect_Release( effect->effect );
|
|
|
|
free( effect );
|
|
|
|
}
|
2022-11-27 21:03:06 +00:00
|
|
|
}
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
static void set_selected_device( IDirectInputDevice8W *device )
|
2022-11-27 21:03:06 +00:00
|
|
|
{
|
2022-11-29 20:20:41 +00:00
|
|
|
IDirectInputDevice8W *previous;
|
|
|
|
|
2022-11-27 21:03:06 +00:00
|
|
|
EnterCriticalSection( &state_cs );
|
2022-11-29 20:20:41 +00:00
|
|
|
|
|
|
|
set_selected_effect( NULL );
|
|
|
|
|
|
|
|
if ((previous = device_selected))
|
|
|
|
{
|
|
|
|
IDirectInputDevice8_SetEventNotification( previous, NULL );
|
|
|
|
IDirectInputDevice8_Release( previous );
|
|
|
|
}
|
|
|
|
if ((device_selected = device))
|
|
|
|
{
|
|
|
|
IDirectInputDevice8_AddRef( device );
|
|
|
|
IDirectInputDevice8_SetEventNotification( device, state_event );
|
|
|
|
IDirectInputDevice8_Acquire( device );
|
|
|
|
}
|
|
|
|
|
2022-11-27 21:03:06 +00:00
|
|
|
LeaveCriticalSection( &state_cs );
|
|
|
|
}
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
static IDirectInputDevice8W *get_selected_device(void)
|
|
|
|
{
|
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
|
|
|
|
EnterCriticalSection( &state_cs );
|
|
|
|
device = device_selected;
|
|
|
|
if (device) IDirectInputDevice8_AddRef( device );
|
|
|
|
LeaveCriticalSection( &state_cs );
|
|
|
|
|
|
|
|
return device;
|
|
|
|
}
|
|
|
|
|
|
|
|
static BOOL CALLBACK enum_devices( const DIDEVICEINSTANCEW *instance, void *context )
|
|
|
|
{
|
|
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
|
|
IDirectInput8W *dinput = context;
|
|
|
|
struct device *entry;
|
|
|
|
|
|
|
|
if (!(entry = calloc( 1, sizeof(*entry) ))) return DIENUM_STOP;
|
|
|
|
|
|
|
|
IDirectInput8_CreateDevice( dinput, &instance->guidInstance, &entry->device, NULL );
|
|
|
|
IDirectInputDevice8_SetDataFormat( entry->device, &c_dfDIJoystick );
|
|
|
|
IDirectInputDevice8_GetCapabilities( entry->device, &caps );
|
|
|
|
|
|
|
|
list_add_tail( &devices, &entry->entry );
|
|
|
|
|
|
|
|
return DIENUM_CONTINUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void clear_devices(void)
|
|
|
|
{
|
|
|
|
struct device *entry, *next;
|
|
|
|
|
|
|
|
set_selected_device( NULL );
|
|
|
|
|
|
|
|
LIST_FOR_EACH_ENTRY_SAFE( entry, next, &devices, struct device, entry )
|
|
|
|
{
|
|
|
|
list_remove( &entry->entry );
|
|
|
|
IDirectInputDevice8_Unacquire( entry->device );
|
|
|
|
IDirectInputDevice8_Release( entry->device );
|
|
|
|
free( entry );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static DWORD WINAPI input_thread( void *param )
|
|
|
|
{
|
|
|
|
HANDLE events[2] = {param, state_event};
|
|
|
|
|
|
|
|
while (WaitForMultipleObjects( 2, events, FALSE, INFINITE ) != 0)
|
|
|
|
{
|
|
|
|
IDirectInputEffect *effect;
|
|
|
|
DIJOYSTATE state = {0};
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
SendMessageW( dialog_hwnd, WM_USER, 0, 0 );
|
|
|
|
|
|
|
|
if ((effect = get_selected_effect()))
|
|
|
|
{
|
|
|
|
DWORD flags = DIEP_AXES | DIEP_DIRECTION | DIEP_NORESTART;
|
|
|
|
LONG direction[3] = {0};
|
|
|
|
DWORD axes[3] = {0};
|
|
|
|
DIEFFECT params =
|
|
|
|
{
|
|
|
|
.dwSize = sizeof(DIEFFECT),
|
|
|
|
.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS,
|
|
|
|
.rglDirection = direction,
|
|
|
|
.rgdwAxes = axes,
|
|
|
|
.cAxes = 3,
|
|
|
|
};
|
|
|
|
|
|
|
|
IDirectInputEffect_GetParameters( effect, ¶ms, flags );
|
|
|
|
params.rgdwAxes[0] = state.lX;
|
|
|
|
params.rgdwAxes[1] = state.lY;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(state.rgbButtons); i++)
|
|
|
|
{
|
|
|
|
if (state.rgbButtons[i])
|
|
|
|
{
|
|
|
|
IDirectInputEffect_SetParameters( effect, ¶ms, flags );
|
|
|
|
IDirectInputEffect_Start( effect, 1, 0 );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
IDirectInputEffect_Release( effect );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-11-27 22:35:23 +00:00
|
|
|
static void draw_axis_view( HDC hdc, RECT rect, const WCHAR *name, LONG value )
|
|
|
|
{
|
|
|
|
POINT center =
|
|
|
|
{
|
|
|
|
.x = (rect.left + rect.right) / 2 + 10,
|
|
|
|
.y = (rect.top + rect.bottom) / 2,
|
|
|
|
};
|
|
|
|
LONG w = (rect.bottom - rect.top + 1) / 3;
|
|
|
|
LONG x = rect.left + 20 + (w + 1) / 2 + MulDiv( value, rect.right - rect.left - 20 - w, 0xffff );
|
|
|
|
COLORREF color;
|
|
|
|
HFONT font;
|
|
|
|
|
|
|
|
FillRect( hdc, &rect, (HBRUSH)(COLOR_WINDOW + 1) );
|
|
|
|
|
|
|
|
color = SetTextColor( hdc, GetSysColor( COLOR_WINDOWTEXT ) );
|
|
|
|
font = SelectObject( hdc, GetStockObject( ANSI_VAR_FONT ) );
|
|
|
|
DrawTextW( hdc, name, -1, &rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP );
|
|
|
|
SetTextColor( hdc, color );
|
|
|
|
SelectObject( hdc, font );
|
|
|
|
|
|
|
|
SetDCBrushColor( hdc, GetSysColor( COLOR_WINDOW ) );
|
|
|
|
SetDCPenColor( hdc, GetSysColor( COLOR_WINDOWFRAME ) );
|
|
|
|
SelectObject( hdc, GetStockObject( DC_BRUSH ) );
|
|
|
|
SelectObject( hdc, GetStockObject( DC_PEN ) );
|
|
|
|
|
|
|
|
RoundRect( hdc, rect.left + 20, rect.top, rect.right, rect.bottom, 5, 5 );
|
|
|
|
|
|
|
|
if (x < center.x)
|
|
|
|
{
|
|
|
|
MoveToEx( hdc, center.x, center.y - 3, NULL );
|
|
|
|
LineTo( hdc, center.x, center.y + 3 );
|
|
|
|
}
|
|
|
|
|
|
|
|
SetDCBrushColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
|
|
|
|
SetDCPenColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
|
|
|
|
|
|
|
|
Rectangle( hdc, rect.left + 20, rect.top + w, x, rect.bottom - w );
|
|
|
|
|
|
|
|
if (x > center.x)
|
|
|
|
{
|
|
|
|
MoveToEx( hdc, center.x, center.y - 3, NULL );
|
|
|
|
LineTo( hdc, center.x, center.y + 3 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_pov_view( HDC hdc, RECT rect, DWORD value )
|
|
|
|
{
|
|
|
|
POINT points[] =
|
|
|
|
{
|
|
|
|
/* 0° */
|
|
|
|
{.x = round( rect.left * 0.71 + rect.right * 0.29 ), .y = round( rect.top * 1.00 + rect.bottom * 0.00 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 45° */
|
|
|
|
{.x = round( rect.left * 0.29 + rect.right * 0.71 ), .y = round( rect.top * 1.00 + rect.bottom * 0.00 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 90° */
|
|
|
|
{.x = round( rect.left * 0.00 + rect.right * 1.00 ), .y = round( rect.top * 0.71 + rect.bottom * 0.29 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 135° */
|
|
|
|
{.x = round( rect.left * 0.00 + rect.right * 1.00 ), .y = round( rect.top * 0.29 + rect.bottom * 0.71 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 180° */
|
|
|
|
{.x = round( rect.left * 0.29 + rect.right * 0.71 ), .y = round( rect.top * 0.00 + rect.bottom * 1.00 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 225° */
|
|
|
|
{.x = round( rect.left * 0.71 + rect.right * 0.29 ), .y = round( rect.top * 0.00 + rect.bottom * 1.00 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 270° */
|
|
|
|
{.x = round( rect.left * 1.00 + rect.right * 0.00 ), .y = round( rect.top * 0.29 + rect.bottom * 0.71 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 315° */
|
|
|
|
{.x = round( rect.left * 1.00 + rect.right * 0.00 ), .y = round( rect.top * 0.71 + rect.bottom * 0.29 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
/* 360° */
|
|
|
|
{.x = round( rect.left * 0.71 + rect.right * 0.29 ), .y = round( rect.top * 1.00 + rect.bottom * 0.00 )},
|
|
|
|
{.x = round( rect.left * 0.50 + rect.right * 0.50 ), .y = round( rect.top * 0.50 + rect.bottom * 0.50 )},
|
|
|
|
{.x = round( rect.left * 0.71 + rect.right * 0.29 ), .y = round( rect.top * 1.00 + rect.bottom * 0.00 )},
|
|
|
|
};
|
|
|
|
DWORD i;
|
|
|
|
|
|
|
|
FillRect( hdc, &rect, (HBRUSH)(COLOR_WINDOW + 1) );
|
|
|
|
|
|
|
|
SetDCBrushColor( hdc, GetSysColor( COLOR_WINDOW ) );
|
|
|
|
SetDCPenColor( hdc, GetSysColor( COLOR_WINDOWFRAME ) );
|
|
|
|
SelectObject( hdc, GetStockObject( DC_BRUSH ) );
|
|
|
|
SelectObject( hdc, GetStockObject( DC_PEN ) );
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(points) - 1; i += 2)
|
|
|
|
{
|
|
|
|
MoveToEx( hdc, (points[i].x + points[i + 1].x) / 2, (points[i].y + points[i + 1].y) / 2, NULL );
|
|
|
|
LineTo( hdc, points[i].x, points[i].y );
|
|
|
|
}
|
|
|
|
|
|
|
|
SetDCPenColor( hdc, GetSysColor( (value != -1) ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWFRAME ) );
|
|
|
|
SetDCBrushColor( hdc, GetSysColor( (value != -1) ? COLOR_HIGHLIGHT : COLOR_WINDOW ) );
|
|
|
|
if (value != -1) Polygon( hdc, points + value / 4500 * 2, 3 );
|
|
|
|
}
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
static void draw_button_view( HDC hdc, RECT rect, BOOL set, const WCHAR *name )
|
2022-11-27 21:03:06 +00:00
|
|
|
{
|
|
|
|
COLORREF color;
|
|
|
|
HFONT font;
|
|
|
|
INT mode;
|
|
|
|
|
|
|
|
FillRect( hdc, &rect, (HBRUSH)(COLOR_WINDOW + 1) );
|
|
|
|
|
|
|
|
SetDCBrushColor( hdc, GetSysColor( set ? COLOR_HIGHLIGHT : COLOR_WINDOW ) );
|
|
|
|
SetDCPenColor( hdc, GetSysColor( set ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWFRAME ) );
|
|
|
|
SelectObject( hdc, GetStockObject( DC_BRUSH ) );
|
|
|
|
SelectObject( hdc, GetStockObject( DC_PEN ) );
|
|
|
|
|
|
|
|
Ellipse( hdc, rect.left, rect.top, rect.right, rect.bottom );
|
|
|
|
|
|
|
|
color = SetTextColor( hdc, GetSysColor( set ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT ) );
|
|
|
|
font = SelectObject( hdc, GetStockObject( ANSI_VAR_FONT ) );
|
|
|
|
mode = SetBkMode( hdc, TRANSPARENT );
|
|
|
|
DrawTextW( hdc, name, -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_NOCLIP );
|
|
|
|
SetBkMode( hdc, mode );
|
|
|
|
SetTextColor( hdc, color );
|
|
|
|
SelectObject( hdc, font );
|
|
|
|
}
|
|
|
|
|
2022-11-27 22:35:23 +00:00
|
|
|
LRESULT CALLBACK test_di_axes_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
|
|
|
|
{
|
|
|
|
TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam );
|
|
|
|
|
|
|
|
if (msg == WM_PAINT)
|
|
|
|
{
|
2022-11-29 20:20:41 +00:00
|
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
DIJOYSTATE state = {0};
|
2022-11-27 22:35:23 +00:00
|
|
|
RECT rect, tmp_rect;
|
|
|
|
PAINTSTRUCT paint;
|
|
|
|
HDC hdc;
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
if ((device = get_selected_device()))
|
|
|
|
{
|
|
|
|
IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state );
|
|
|
|
IDirectInputDevice8_GetCapabilities( device, &caps );
|
|
|
|
IDirectInputDevice8_Release( device );
|
|
|
|
}
|
2022-11-27 22:35:23 +00:00
|
|
|
|
|
|
|
hdc = BeginPaint( hwnd, &paint );
|
|
|
|
|
|
|
|
GetClientRect( hwnd, &rect );
|
|
|
|
rect.bottom = rect.top + (rect.bottom - rect.top - 2) / 4 - 2;
|
|
|
|
rect.right = rect.left + (rect.right - rect.left) / 2 - 10;
|
|
|
|
|
|
|
|
OffsetRect( &rect, 5, 2 );
|
|
|
|
draw_axis_view( hdc, rect, L"X", state.lX );
|
|
|
|
|
|
|
|
tmp_rect = rect;
|
|
|
|
OffsetRect( &rect, rect.right - rect.left + 10, 0 );
|
|
|
|
draw_axis_view( hdc, rect, L"Rx", state.lRx );
|
|
|
|
rect = tmp_rect;
|
|
|
|
|
|
|
|
OffsetRect( &rect, 0, rect.bottom - rect.top + 2 );
|
|
|
|
draw_axis_view( hdc, rect, L"Y", state.lY );
|
|
|
|
|
|
|
|
tmp_rect = rect;
|
|
|
|
OffsetRect( &rect, rect.right - rect.left + 10, 0 );
|
|
|
|
draw_axis_view( hdc, rect, L"Ry", state.lRy );
|
|
|
|
rect = tmp_rect;
|
|
|
|
|
|
|
|
OffsetRect( &rect, 0, rect.bottom - rect.top + 2 );
|
|
|
|
draw_axis_view( hdc, rect, L"Z", state.lZ );
|
|
|
|
|
|
|
|
tmp_rect = rect;
|
|
|
|
OffsetRect( &rect, rect.right - rect.left + 10, 0 );
|
|
|
|
draw_axis_view( hdc, rect, L"Rz", state.lRz );
|
|
|
|
rect = tmp_rect;
|
|
|
|
|
|
|
|
OffsetRect( &rect, 0, rect.bottom - rect.top + 2 );
|
|
|
|
draw_axis_view( hdc, rect, L"S", state.rglSlider[0] );
|
|
|
|
|
|
|
|
tmp_rect = rect;
|
|
|
|
OffsetRect( &rect, rect.right - rect.left + 10, 0 );
|
|
|
|
draw_axis_view( hdc, rect, L"Rs", state.rglSlider[1] );
|
|
|
|
rect = tmp_rect;
|
|
|
|
|
|
|
|
EndPaint( hwnd, &paint );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return DefWindowProcW( hwnd, msg, wparam, lparam );
|
|
|
|
}
|
|
|
|
|
|
|
|
LRESULT CALLBACK test_di_povs_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
|
|
|
|
{
|
|
|
|
TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam );
|
|
|
|
|
|
|
|
if (msg == WM_PAINT)
|
|
|
|
{
|
2022-11-29 20:20:41 +00:00
|
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
DIJOYSTATE state = {0};
|
2022-11-27 22:35:23 +00:00
|
|
|
PAINTSTRUCT paint;
|
|
|
|
RECT rect;
|
|
|
|
HDC hdc;
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
if ((device = get_selected_device()))
|
|
|
|
{
|
|
|
|
IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state );
|
|
|
|
IDirectInputDevice8_GetCapabilities( device, &caps );
|
|
|
|
IDirectInputDevice8_Release( device );
|
|
|
|
}
|
2022-11-27 22:35:23 +00:00
|
|
|
|
|
|
|
hdc = BeginPaint( hwnd, &paint );
|
|
|
|
|
|
|
|
GetClientRect( hwnd, &rect );
|
|
|
|
rect.bottom = rect.top + (rect.bottom - rect.top - 5) / 2 - 5;
|
|
|
|
rect.right = rect.left + (rect.bottom - rect.top);
|
|
|
|
|
|
|
|
OffsetRect( &rect, 5, 5 );
|
|
|
|
draw_pov_view( hdc, rect, state.rgdwPOV[0] );
|
|
|
|
OffsetRect( &rect, rect.right - rect.left + 5, 0 );
|
|
|
|
draw_pov_view( hdc, rect, state.rgdwPOV[1] );
|
|
|
|
OffsetRect( &rect, rect.left - rect.right - 5, rect.bottom - rect.top + 5 );
|
|
|
|
draw_pov_view( hdc, rect, state.rgdwPOV[1] );
|
|
|
|
OffsetRect( &rect, rect.right - rect.left + 5, 0 );
|
|
|
|
draw_pov_view( hdc, rect, state.rgdwPOV[2] );
|
|
|
|
|
|
|
|
EndPaint( hwnd, &paint );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return DefWindowProcW( hwnd, msg, wparam, lparam );
|
|
|
|
}
|
|
|
|
|
2022-11-27 21:03:06 +00:00
|
|
|
LRESULT CALLBACK test_di_buttons_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
|
|
|
|
{
|
|
|
|
TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam );
|
|
|
|
|
|
|
|
if (msg == WM_PAINT)
|
|
|
|
{
|
2022-11-29 20:20:41 +00:00
|
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
2022-11-27 21:03:06 +00:00
|
|
|
UINT i, j, offs, size, step, space = 2;
|
2022-11-29 20:20:41 +00:00
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
DIJOYSTATE state = {0};
|
2022-11-27 21:03:06 +00:00
|
|
|
PAINTSTRUCT paint;
|
|
|
|
RECT rect;
|
|
|
|
HDC hdc;
|
|
|
|
|
2022-11-29 20:20:41 +00:00
|
|
|
if ((device = get_selected_device()))
|
|
|
|
{
|
|
|
|
IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state );
|
|
|
|
IDirectInputDevice8_GetCapabilities( device, &caps );
|
|
|
|
IDirectInputDevice8_Release( device );
|
|
|
|
}
|
2022-11-27 21:03:06 +00:00
|
|
|
|
|
|
|
if (caps.dwButtons <= 48) step = 16;
|
|
|
|
else step = 32;
|
|
|
|
|
|
|
|
hdc = BeginPaint( hwnd, &paint );
|
|
|
|
|
|
|
|
GetClientRect( hwnd, &rect );
|
|
|
|
|
|
|
|
size = (rect.right - rect.left - space) / step;
|
|
|
|
offs = (rect.right - rect.left - step * size - space) / 2;
|
|
|
|
OffsetRect( &rect, offs, offs );
|
|
|
|
rect.right = rect.left + size - space;
|
|
|
|
rect.bottom = rect.top + size - space;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(state.rgbButtons) && i < caps.dwButtons;)
|
|
|
|
{
|
|
|
|
RECT first = rect;
|
|
|
|
|
|
|
|
for (j = 0; j < step && i < caps.dwButtons; j++, i++)
|
|
|
|
{
|
|
|
|
WCHAR buffer[3];
|
|
|
|
swprintf( buffer, ARRAY_SIZE(buffer), L"%d", i );
|
|
|
|
draw_button_view( hdc, rect, state.rgbButtons[i], buffer );
|
|
|
|
OffsetRect( &rect, size, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
rect = first;
|
|
|
|
OffsetRect( &rect, 0, size );
|
|
|
|
}
|
|
|
|
|
|
|
|
EndPaint( hwnd, &paint );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return DefWindowProcW( hwnd, msg, wparam, lparam );
|
|
|
|
}
|
2022-11-29 20:20:41 +00:00
|
|
|
|
|
|
|
static void update_di_effects( HWND hwnd, IDirectInputDevice8W *device )
|
|
|
|
{
|
|
|
|
struct effect *effect;
|
|
|
|
|
|
|
|
clear_effects();
|
|
|
|
|
|
|
|
IDirectInputDevice8_EnumEffects( device, enum_effects, device, 0 );
|
|
|
|
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_EFFECTS, LB_RESETCONTENT, 0, 0 );
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_EFFECTS, LB_ADDSTRING, 0, (LPARAM)L"None" );
|
|
|
|
|
|
|
|
LIST_FOR_EACH_ENTRY( effect, &effects, struct effect, entry )
|
|
|
|
{
|
|
|
|
DIEFFECTINFOW info = {.dwSize = sizeof(DIEFFECTINFOW)};
|
|
|
|
GUID guid;
|
|
|
|
|
|
|
|
if (FAILED(IDirectInputEffect_GetEffectGuid( effect->effect, &guid ))) continue;
|
|
|
|
if (FAILED(IDirectInputDevice8_GetEffectInfo( device, &info, &guid ))) continue;
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_EFFECTS, LB_ADDSTRING, 0, (LPARAM)( info.tszName + 5 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_di_effects_change( HWND hwnd )
|
|
|
|
{
|
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
struct list *entry;
|
|
|
|
int sel;
|
|
|
|
|
|
|
|
set_selected_effect( NULL );
|
|
|
|
|
|
|
|
sel = SendDlgItemMessageW( hwnd, IDC_DI_EFFECTS, LB_GETCURSEL, 0, 0 ) - 1;
|
|
|
|
if (sel < 0) return;
|
|
|
|
|
|
|
|
entry = list_head( &effects );
|
|
|
|
while (sel-- && entry) entry = list_next( &effects, entry );
|
|
|
|
if (!entry) return;
|
|
|
|
|
|
|
|
set_selected_effect( LIST_ENTRY( entry, struct effect, entry )->effect );
|
|
|
|
|
|
|
|
if ((device = get_selected_device()))
|
|
|
|
{
|
|
|
|
IDirectInputDevice8_Unacquire( device );
|
|
|
|
IDirectInputDevice8_SetCooperativeLevel( device, GetAncestor( hwnd, GA_ROOT ), DISCL_BACKGROUND | DISCL_EXCLUSIVE );
|
|
|
|
IDirectInputDevice8_Acquire( device );
|
|
|
|
IDirectInputDevice8_Release( device );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void create_device_views( HWND hwnd )
|
|
|
|
{
|
|
|
|
HINSTANCE instance = (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE );
|
|
|
|
HWND parent;
|
|
|
|
LONG margin;
|
|
|
|
RECT rect;
|
|
|
|
|
|
|
|
parent = GetDlgItem( hwnd, IDC_DI_AXES );
|
|
|
|
GetClientRect( parent, &rect );
|
|
|
|
rect.top += 10;
|
|
|
|
|
|
|
|
margin = (rect.bottom - rect.top) * 10 / 100;
|
|
|
|
InflateRect( &rect, -margin, -margin );
|
|
|
|
|
|
|
|
CreateWindowW( L"JoyCplDInputAxes", NULL, WS_CHILD | WS_VISIBLE, rect.left, rect.top,
|
|
|
|
rect.right - rect.left, rect.bottom - rect.top, parent, NULL, NULL, instance );
|
|
|
|
|
|
|
|
parent = GetDlgItem( hwnd, IDC_DI_POVS );
|
|
|
|
GetClientRect( parent, &rect );
|
|
|
|
rect.top += 10;
|
|
|
|
|
|
|
|
margin = (rect.bottom - rect.top) * 10 / 100;
|
|
|
|
InflateRect( &rect, -margin, -margin );
|
|
|
|
|
|
|
|
CreateWindowW( L"JoyCplDInputPOVs", NULL, WS_CHILD | WS_VISIBLE, rect.left, rect.top,
|
|
|
|
rect.right - rect.left, rect.bottom - rect.top, parent, NULL, NULL, instance );
|
|
|
|
|
|
|
|
parent = GetDlgItem( hwnd, IDC_DI_BUTTONS );
|
|
|
|
GetClientRect( parent, &rect );
|
|
|
|
rect.top += 10;
|
|
|
|
|
|
|
|
margin = (rect.bottom - rect.top) * 10 / 100;
|
|
|
|
InflateRect( &rect, -margin, -margin );
|
|
|
|
|
|
|
|
CreateWindowW( L"JoyCplDInputButtons", NULL, WS_CHILD | WS_VISIBLE, rect.left, rect.top,
|
|
|
|
rect.right - rect.left, rect.bottom - rect.top, parent, NULL, NULL, instance );
|
|
|
|
}
|
|
|
|
|
|
|
|
static void handle_di_devices_change( HWND hwnd )
|
|
|
|
{
|
|
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
|
|
IDirectInputDevice8W *device;
|
|
|
|
struct list *entry;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
set_selected_device( NULL );
|
|
|
|
|
|
|
|
i = SendDlgItemMessageW( hwnd, IDC_DI_DEVICES, CB_GETCURSEL, 0, 0 );
|
|
|
|
if (i < 0) return;
|
|
|
|
|
|
|
|
entry = list_head( &devices );
|
|
|
|
while (i-- && entry) entry = list_next( &devices, entry );
|
|
|
|
if (!entry) return;
|
|
|
|
|
|
|
|
device = LIST_ENTRY( entry, struct device, entry )->device;
|
|
|
|
if (FAILED(IDirectInputDevice8_GetCapabilities( device, &caps ))) return;
|
|
|
|
|
|
|
|
set_selected_device( device );
|
|
|
|
update_di_effects( hwnd, device );
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_di_devices( HWND hwnd )
|
|
|
|
{
|
|
|
|
IDirectInput8W *dinput;
|
|
|
|
struct device *entry;
|
|
|
|
|
|
|
|
clear_devices();
|
|
|
|
|
|
|
|
DirectInput8Create( GetModuleHandleW( NULL ), DIRECTINPUT_VERSION, &IID_IDirectInput8W, (void **)&dinput, NULL );
|
|
|
|
IDirectInput8_EnumDevices( dinput, DI8DEVCLASS_GAMECTRL, enum_devices, dinput, DIEDFL_ATTACHEDONLY );
|
|
|
|
IDirectInput8_Release( dinput );
|
|
|
|
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_DEVICES, CB_RESETCONTENT, 0, 0 );
|
|
|
|
|
|
|
|
LIST_FOR_EACH_ENTRY( entry, &devices, struct device, entry )
|
|
|
|
{
|
|
|
|
DIDEVICEINSTANCEW info = {.dwSize = sizeof(DIDEVICEINSTANCEW)};
|
|
|
|
if (FAILED(IDirectInputDevice8_GetDeviceInfo( entry->device, &info ))) continue;
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_DEVICES, CB_ADDSTRING, 0, (LPARAM)info.tszInstanceName );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_device_views( HWND hwnd )
|
|
|
|
{
|
|
|
|
HWND parent, view;
|
|
|
|
|
|
|
|
parent = GetDlgItem( hwnd, IDC_DI_AXES );
|
|
|
|
view = FindWindowExW( parent, NULL, L"JoyCplDInputAxes", NULL );
|
|
|
|
InvalidateRect( view, NULL, TRUE );
|
|
|
|
|
|
|
|
parent = GetDlgItem( hwnd, IDC_DI_POVS );
|
|
|
|
view = FindWindowExW( parent, NULL, L"JoyCplDInputPOVs", NULL );
|
|
|
|
InvalidateRect( view, NULL, TRUE );
|
|
|
|
|
|
|
|
parent = GetDlgItem( hwnd, IDC_DI_BUTTONS );
|
|
|
|
view = FindWindowExW( parent, NULL, L"JoyCplDInputButtons", NULL );
|
|
|
|
InvalidateRect( view, NULL, TRUE );
|
|
|
|
}
|
|
|
|
|
|
|
|
INT_PTR CALLBACK test_di_dialog_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
|
|
|
|
{
|
|
|
|
static HANDLE thread, thread_stop;
|
|
|
|
|
|
|
|
TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam );
|
|
|
|
|
|
|
|
switch (msg)
|
|
|
|
{
|
|
|
|
case WM_INITDIALOG:
|
|
|
|
create_device_views( hwnd );
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_COMMAND:
|
|
|
|
switch (wparam)
|
|
|
|
{
|
|
|
|
case MAKEWPARAM( IDC_DI_DEVICES, CBN_SELCHANGE ):
|
|
|
|
handle_di_devices_change( hwnd );
|
|
|
|
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_EFFECTS, LB_SETCURSEL, 0, 0 );
|
|
|
|
handle_di_effects_change( hwnd );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MAKEWPARAM( IDC_DI_EFFECTS, LBN_SELCHANGE ):
|
|
|
|
handle_di_effects_change( hwnd );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_NOTIFY:
|
|
|
|
switch (((NMHDR *)lparam)->code)
|
|
|
|
{
|
|
|
|
case PSN_SETACTIVE:
|
|
|
|
dialog_hwnd = hwnd;
|
|
|
|
state_event = CreateEventW( NULL, FALSE, FALSE, NULL );
|
|
|
|
thread_stop = CreateEventW( NULL, FALSE, FALSE, NULL );
|
|
|
|
|
|
|
|
update_di_devices( hwnd );
|
|
|
|
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_DEVICES, CB_SETCURSEL, 0, 0 );
|
|
|
|
handle_di_devices_change( hwnd );
|
|
|
|
|
|
|
|
SendDlgItemMessageW( hwnd, IDC_DI_EFFECTS, LB_SETCURSEL, 0, 0 );
|
|
|
|
handle_di_effects_change( hwnd );
|
|
|
|
|
|
|
|
thread = CreateThread( NULL, 0, input_thread, (void *)thread_stop, 0, NULL );
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PSN_RESET:
|
|
|
|
case PSN_KILLACTIVE:
|
|
|
|
SetEvent( thread_stop );
|
|
|
|
MsgWaitForMultipleObjects( 1, &thread, FALSE, INFINITE, 0 );
|
|
|
|
CloseHandle( state_event );
|
|
|
|
CloseHandle( thread_stop );
|
|
|
|
CloseHandle( thread );
|
|
|
|
|
|
|
|
clear_effects();
|
|
|
|
clear_devices();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_USER:
|
|
|
|
update_device_views( hwnd );
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
|
|
}
|