2 * Copyright 2022 RĂ©mi Bernon for CodeWeavers
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
32 #include "wine/debug.h"
33 #include "wine/list.h"
35 #include "joy_private.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(joycpl
);
41 XINPUT_CAPABILITIES caps
;
47 static CRITICAL_SECTION state_cs
;
48 static CRITICAL_SECTION_DEBUG state_cs_debug
=
51 { &state_cs_debug
.ProcessLocksList
, &state_cs_debug
.ProcessLocksList
},
52 0, 0, { (DWORD_PTR
)(__FILE__
": state_cs") }
54 static CRITICAL_SECTION state_cs
= { &state_cs_debug
, -1, 0, 0, 0, 0 };
56 static struct device_state devices_state
[XUSER_MAX_COUNT
] =
58 {.status
= ERROR_DEVICE_NOT_CONNECTED
},
59 {.status
= ERROR_DEVICE_NOT_CONNECTED
},
60 {.status
= ERROR_DEVICE_NOT_CONNECTED
},
61 {.status
= ERROR_DEVICE_NOT_CONNECTED
},
63 static HWND dialog_hwnd
;
65 static void set_device_state( DWORD index
, struct device_state
*state
)
69 EnterCriticalSection( &state_cs
);
70 state
->rumble
= devices_state
[index
].rumble
;
71 modified
= memcmp( devices_state
+ index
, state
, sizeof(*state
) );
72 devices_state
[index
] = *state
;
73 LeaveCriticalSection( &state_cs
);
75 if (modified
) SendMessageW( dialog_hwnd
, WM_USER
, index
, 0 );
78 static void get_device_state( DWORD index
, struct device_state
*state
)
80 EnterCriticalSection( &state_cs
);
81 *state
= devices_state
[index
];
82 LeaveCriticalSection( &state_cs
);
85 static DWORD WINAPI
input_thread_proc( void *param
)
87 HANDLE thread_stop
= param
;
90 while (WaitForSingleObject( thread_stop
, 20 ) == WAIT_TIMEOUT
)
92 for (i
= 0; i
< ARRAY_SIZE(devices_state
); ++i
)
94 XINPUT_VIBRATION vibration
= {0};
95 struct device_state state
= {0};
97 state
.status
= XInputGetCapabilities( i
, 0, &state
.caps
);
98 if (!state
.status
) state
.status
= XInputGetState( i
, &state
.state
);
99 set_device_state( i
, &state
);
103 vibration
.wLeftMotorSpeed
= 2 * max( abs( state
.state
.Gamepad
.sThumbLX
),
104 abs( state
.state
.Gamepad
.sThumbLY
) ) - 1;
105 vibration
.wRightMotorSpeed
= 2 * max( abs( state
.state
.Gamepad
.sThumbRX
),
106 abs( state
.state
.Gamepad
.sThumbRY
) ) - 1;
109 XInputSetState( i
, &vibration
);
116 static void draw_axis_view( HDC hdc
, RECT rect
, SHORT dx
, SHORT dy
, BOOL set
)
120 .x
= (rect
.left
+ rect
.right
) / 2,
121 .y
= (rect
.top
+ rect
.bottom
) / 2,
125 .x
= center
.x
+ MulDiv( dx
, rect
.right
- rect
.left
- 20, 0xffff ),
126 .y
= center
.y
- MulDiv( dy
, rect
.bottom
- rect
.top
- 20, 0xffff ),
129 FillRect( hdc
, &rect
, (HBRUSH
)(COLOR_WINDOW
+ 1) );
131 SetDCBrushColor( hdc
, GetSysColor( set
? COLOR_HIGHLIGHT
: COLOR_WINDOW
) );
132 SetDCPenColor( hdc
, GetSysColor( set
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWFRAME
) );
133 SelectObject( hdc
, GetStockObject( DC_BRUSH
) );
134 SelectObject( hdc
, GetStockObject( DC_PEN
) );
136 Ellipse( hdc
, rect
.left
, rect
.top
, rect
.right
, rect
.bottom
);
138 MoveToEx( hdc
, center
.x
, center
.y
- 3, NULL
);
139 LineTo( hdc
, center
.x
, center
.y
+ 4 );
140 MoveToEx( hdc
, center
.x
- 3, center
.y
, NULL
);
141 LineTo( hdc
, center
.x
+ 4, center
.y
);
143 if (!set
) SetDCPenColor( hdc
, GetSysColor( (dx
|| dy
) ? COLOR_HIGHLIGHT
: COLOR_WINDOWFRAME
) );
145 MoveToEx( hdc
, center
.x
, center
.y
, NULL
);
146 LineTo( hdc
, pos
.x
, pos
.y
);
148 Ellipse( hdc
, pos
.x
- 4, pos
.y
- 4, pos
.x
+ 4, pos
.y
+ 4 );
151 static void draw_trigger_view( HDC hdc
, RECT rect
, BYTE dt
)
155 .x
= (rect
.left
+ rect
.right
) / 2,
156 .y
= (rect
.top
+ rect
.bottom
) / 2,
158 LONG w
= (rect
.right
- rect
.left
+ 1) / 3;
159 LONG y
= rect
.bottom
- (w
+ 1) / 2 - MulDiv( dt
, rect
.bottom
- rect
.top
- w
, 0xff );
161 FillRect( hdc
, &rect
, (HBRUSH
)(COLOR_WINDOW
+ 1) );
163 SetDCBrushColor( hdc
, GetSysColor( COLOR_WINDOW
) );
164 SetDCPenColor( hdc
, GetSysColor( COLOR_WINDOWFRAME
) );
165 SelectObject( hdc
, GetStockObject( DC_BRUSH
) );
166 SelectObject( hdc
, GetStockObject( DC_PEN
) );
168 RoundRect( hdc
, rect
.left
, rect
.top
, rect
.right
, rect
.bottom
, 5, 5 );
172 MoveToEx( hdc
, center
.x
- 3, center
.y
, NULL
);
173 LineTo( hdc
, center
.x
+ 3, center
.y
);
176 SetDCBrushColor( hdc
, GetSysColor( dt
? COLOR_HIGHLIGHT
: COLOR_WINDOW
) );
177 SetDCPenColor( hdc
, GetSysColor( dt
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWFRAME
) );
179 Rectangle( hdc
, center
.x
- w
, y
, center
.x
+ w
, rect
.bottom
);
183 MoveToEx( hdc
, center
.x
- 3, center
.y
, NULL
);
184 LineTo( hdc
, center
.x
+ 3, center
.y
);
188 static void draw_button_view( HDC hdc
, RECT rect
, BOOL set
, const WCHAR
*name
)
190 COLORREF color
= SetTextColor( hdc
, GetSysColor( set
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWTEXT
) );
191 HFONT font
= SelectObject( hdc
, GetStockObject( ANSI_VAR_FONT
) );
192 INT mode
= SetBkMode( hdc
, TRANSPARENT
);
194 FillRect( hdc
, &rect
, (HBRUSH
)(COLOR_WINDOW
+ 1) );
196 SetDCBrushColor( hdc
, GetSysColor( set
? COLOR_HIGHLIGHT
: COLOR_WINDOW
) );
197 SetDCPenColor( hdc
, GetSysColor( set
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWFRAME
) );
198 SelectObject( hdc
, GetStockObject( DC_BRUSH
) );
199 SelectObject( hdc
, GetStockObject( DC_PEN
) );
201 Ellipse( hdc
, rect
.left
, rect
.top
, rect
.right
, rect
.bottom
);
202 if (name
[0] >= 'A' && name
[0] <= 'Z')
203 DrawTextW( hdc
, name
, -1, &rect
, DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
);
204 else if (name
[0] == '=')
206 RECT tmp_rect
= {.right
= 10, .bottom
= 2};
208 OffsetRect( &tmp_rect
, rect
.left
, rect
.top
);
209 OffsetRect( &tmp_rect
, (rect
.right
- rect
.left
) / 2, (rect
.bottom
- rect
.top
) / 2 );
210 OffsetRect( &tmp_rect
, (tmp_rect
.left
- tmp_rect
.right
) / 2, (tmp_rect
.top
- tmp_rect
.bottom
) / 2 );
212 FillRect( hdc
, &tmp_rect
, (HBRUSH
)((UINT_PTR
)(set
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWTEXT
) + 1) );
213 OffsetRect( &tmp_rect
, 0, 3 * (tmp_rect
.top
- tmp_rect
.bottom
) / 2 );
214 FillRect( hdc
, &tmp_rect
, (HBRUSH
)((UINT_PTR
)(set
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWTEXT
) + 1) );
215 OffsetRect( &tmp_rect
, 0, 6 * (tmp_rect
.bottom
- tmp_rect
.top
) / 2 );
216 FillRect( hdc
, &tmp_rect
, (HBRUSH
)((UINT_PTR
)(set
? COLOR_HIGHLIGHTTEXT
: COLOR_WINDOWTEXT
) + 1) );
223 .lfWeight
= FW_NORMAL
,
224 .lfCharSet
= SYMBOL_CHARSET
,
225 .lfFaceName
= L
"Marlett",
227 WCHAR buffer
[4] = {0};
230 font
= CreateFontIndirectW( &logfont
);
231 font
= (HFONT
)SelectObject( hdc
, font
);
233 if (name
[0] == '#') { buffer
[0] = 0x32; OffsetRect( &rect
, 1, 0 ); }
234 if (name
[0] == '<') buffer
[0] = 0x33;
235 if (name
[0] == '>') buffer
[0] = 0x34;
236 if (name
[0] == '^') buffer
[0] = 0x35;
237 if (name
[0] == 'v') buffer
[0] = 0x36;
238 if (name
[0] == '@') buffer
[0] = 0x6e;
239 DrawTextW( hdc
, buffer
, -1, &rect
, DT_CENTER
| DT_VCENTER
| DT_SINGLELINE
| DT_NOCLIP
);
241 font
= (HFONT
)SelectObject( hdc
, font
);
242 DeleteObject( font
);
245 SetBkMode( hdc
, mode
);
246 SetTextColor( hdc
, color
);
247 SelectObject( hdc
, font
);
250 LRESULT CALLBACK
test_xi_window_proc( HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
252 TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd
, msg
, wparam
, lparam
);
256 DWORD index
= GetWindowLongW( hwnd
, GWLP_USERDATA
);
257 UINT axis_size
, trigger_size
, button_size
, horiz_space
;
258 struct device_state state
;
263 GetClientRect( hwnd
, &rect
);
264 axis_size
= rect
.bottom
- rect
.top
;
265 button_size
= (axis_size
- 1) / 3;
266 trigger_size
= axis_size
/ 4;
267 horiz_space
= (rect
.right
- rect
.left
- axis_size
* 2 - trigger_size
* 2 - button_size
* 5) / 10;
269 get_device_state( index
, &state
);
271 hdc
= BeginPaint( hwnd
, &paint
);
273 rect
.right
= rect
.left
+ axis_size
;
274 OffsetRect( &rect
, horiz_space
, 0 );
275 draw_axis_view( hdc
, rect
, state
.state
.Gamepad
.sThumbLX
, state
.state
.Gamepad
.sThumbLY
,
276 state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_LEFT_THUMB
);
277 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
278 draw_axis_view( hdc
, rect
, state
.state
.Gamepad
.sThumbRX
, state
.state
.Gamepad
.sThumbRY
,
279 state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_RIGHT_THUMB
);
281 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
282 rect
.right
= rect
.left
+ trigger_size
;
283 draw_trigger_view( hdc
, rect
, state
.state
.Gamepad
.bLeftTrigger
);
284 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
285 draw_trigger_view( hdc
, rect
, state
.state
.Gamepad
.bRightTrigger
);
287 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
288 rect
.right
= rect
.left
+ button_size
;
289 rect
.bottom
= rect
.top
+ button_size
;
291 OffsetRect( &rect
, (rect
.right
- rect
.left
+ horiz_space
) / 2, 0 );
292 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_DPAD_UP
, L
"^" );
293 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
294 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_LEFT_SHOULDER
, L
"L" );
295 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
296 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_RIGHT_SHOULDER
, L
"R" );
297 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
298 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_Y
, L
"Y" );
301 OffsetRect( &rect
, 0, rect
.bottom
- rect
.top
);
303 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_DPAD_LEFT
, L
"<" );
304 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
305 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_DPAD_RIGHT
, L
">" );
306 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
307 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& 0x0400, L
"@" );
308 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
309 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_X
, L
"X" );
310 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
311 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_B
, L
"B" );
314 OffsetRect( &rect
, (rect
.right
- rect
.left
+ horiz_space
) / 2, rect
.bottom
- rect
.top
);
315 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_DPAD_DOWN
, L
"v" );
316 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
317 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_BACK
, L
"#" );
318 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
319 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_START
, L
"=" );
320 OffsetRect( &rect
, rect
.right
- rect
.left
+ horiz_space
, 0 );
321 draw_button_view( hdc
, rect
, state
.state
.Gamepad
.wButtons
& XINPUT_GAMEPAD_A
, L
"A" );
323 EndPaint( hwnd
, &paint
);
328 return DefWindowProcW( hwnd
, msg
, wparam
, lparam
);
331 static void create_user_view( HWND hwnd
, DWORD index
)
333 HINSTANCE instance
= (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
);
338 parent
= GetDlgItem( hwnd
, IDC_XI_USER_0
+ index
);
340 GetClientRect( parent
, &rect
);
343 margin
= (rect
.bottom
- rect
.top
) * 15 / 100;
344 InflateRect( &rect
, -margin
, -margin
);
346 view
= CreateWindowW( L
"JoyCplXInput", NULL
, WS_CHILD
| WS_VISIBLE
, rect
.left
, rect
.top
,
347 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
, parent
, NULL
, NULL
, instance
);
348 SetWindowLongW( view
, GWLP_USERDATA
, index
);
350 ShowWindow( parent
, SW_HIDE
);
352 parent
= GetDlgItem( hwnd
, IDC_XI_RUMBLE_0
+ index
);
353 ShowWindow( parent
, SW_HIDE
);
356 static void update_user_view( HWND hwnd
, DWORD index
)
358 struct device_state state
;
361 get_device_state( index
, &state
);
363 parent
= GetDlgItem( hwnd
, IDC_XI_NO_USER_0
+ index
);
364 ShowWindow( parent
, state
.status
? SW_SHOW
: SW_HIDE
);
366 parent
= GetDlgItem( hwnd
, IDC_XI_RUMBLE_0
+ index
);
367 ShowWindow( parent
, state
.status
? SW_HIDE
: SW_SHOW
);
369 parent
= GetDlgItem( hwnd
, IDC_XI_USER_0
+ index
);
370 ShowWindow( parent
, state
.status
? SW_HIDE
: SW_SHOW
);
374 HWND view
= FindWindowExW( parent
, NULL
, L
"JoyCplXInput", NULL
);
375 InvalidateRect( view
, NULL
, TRUE
);
379 static void update_rumble_state( HWND hwnd
, DWORD index
)
384 parent
= GetDlgItem( hwnd
, IDC_XI_RUMBLE_0
+ index
);
385 res
= SendMessageW( parent
, BM_GETCHECK
, 0, 0 );
387 EnterCriticalSection( &state_cs
);
388 devices_state
[index
].rumble
= res
== BST_CHECKED
;
389 LeaveCriticalSection( &state_cs
);
392 extern INT_PTR CALLBACK
test_xi_dialog_proc( HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
394 static HANDLE thread
, thread_stop
;
396 TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd
, msg
, wparam
, lparam
);
401 create_user_view( hwnd
, 0 );
402 create_user_view( hwnd
, 1 );
403 create_user_view( hwnd
, 2 );
404 create_user_view( hwnd
, 3 );
408 switch (LOWORD(wparam
))
410 case IDC_XI_RUMBLE_0
:
411 case IDC_XI_RUMBLE_1
:
412 case IDC_XI_RUMBLE_2
:
413 case IDC_XI_RUMBLE_3
:
414 update_rumble_state( hwnd
, LOWORD(wparam
) - IDC_XI_RUMBLE_0
);
420 switch (((NMHDR
*)lparam
)->code
)
424 thread_stop
= CreateEventW( NULL
, FALSE
, FALSE
, NULL
);
425 thread
= CreateThread( NULL
, 0, input_thread_proc
, (void *)thread_stop
, 0, NULL
);
430 SetEvent( thread_stop
);
431 /* wait for the input thread to stop, processing any WM_USER message from it */
432 while (MsgWaitForMultipleObjects( 1, &thread
, FALSE
, INFINITE
, QS_ALLINPUT
) == 1)
435 while (PeekMessageW( &msg
, 0, 0, 0, PM_REMOVE
))
437 TranslateMessage( &msg
);
438 DispatchMessageW( &msg
);
441 CloseHandle( thread_stop
);
442 CloseHandle( thread
);
449 update_user_view( hwnd
, wparam
);