wine/dlls/joy.cpl/dinput.c
2022-11-30 11:14:07 +01:00

811 lines
26 KiB
C

/*
* 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>
#include <math.h>
#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);
struct effect
{
struct list entry;
IDirectInputEffect *effect;
};
struct device
{
struct list entry;
IDirectInputDevice8W *device;
};
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 };
static struct list effects = LIST_INIT( effects );
static IDirectInputEffect *effect_selected;
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 )
{
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, &params, &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;
EnterCriticalSection( &state_cs );
if ((previous = effect_selected)) IDirectInputEffect_Release( previous );
if ((effect_selected = effect)) IDirectInput_AddRef( effect );
LeaveCriticalSection( &state_cs );
}
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;
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 );
}
}
static void set_selected_device( IDirectInputDevice8W *device )
{
IDirectInputDevice8W *previous;
EnterCriticalSection( &state_cs );
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 );
}
LeaveCriticalSection( &state_cs );
}
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, &params, 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, &params, flags );
IDirectInputEffect_Start( effect, 1, 0 );
break;
}
}
IDirectInputEffect_Release( effect );
}
}
return 0;
}
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 );
}
static void draw_button_view( HDC hdc, RECT rect, BOOL set, const WCHAR *name )
{
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 );
}
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)
{
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
IDirectInputDevice8W *device;
DIJOYSTATE state = {0};
RECT rect, tmp_rect;
PAINTSTRUCT paint;
HDC hdc;
if ((device = get_selected_device()))
{
IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state );
IDirectInputDevice8_GetCapabilities( device, &caps );
IDirectInputDevice8_Release( device );
}
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)
{
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
IDirectInputDevice8W *device;
DIJOYSTATE state = {0};
PAINTSTRUCT paint;
RECT rect;
HDC hdc;
if ((device = get_selected_device()))
{
IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state );
IDirectInputDevice8_GetCapabilities( device, &caps );
IDirectInputDevice8_Release( device );
}
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 );
}
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)
{
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
UINT i, j, offs, size, step, space = 2;
IDirectInputDevice8W *device;
DIJOYSTATE state = {0};
PAINTSTRUCT paint;
RECT rect;
HDC hdc;
if ((device = get_selected_device()))
{
IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state );
IDirectInputDevice8_GetCapabilities( device, &caps );
IDirectInputDevice8_Release( device );
}
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 );
}
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;
}