ntdll: Use a separate memory allocation for the kernel stack.
[wine.git] / dlls / joy.cpl / xinput.c
blobf652c9ad171dce9225904f3bb19dd743d25f5f3c
1 /*
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
20 #include <stdarg.h>
21 #include <stddef.h>
22 #include <stdlib.h>
23 #include <math.h>
25 #include "windef.h"
26 #include "winbase.h"
27 #include "winuser.h"
28 #include "wingdi.h"
30 #include "xinput.h"
32 #include "wine/debug.h"
33 #include "wine/list.h"
35 #include "joy_private.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(joycpl);
39 struct device_state
41 XINPUT_CAPABILITIES caps;
42 XINPUT_STATE state;
43 DWORD status;
44 BOOL rumble;
47 static CRITICAL_SECTION state_cs;
48 static CRITICAL_SECTION_DEBUG state_cs_debug =
50 0, 0, &state_cs,
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 )
67 BOOL modified;
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;
88 DWORD i;
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 );
101 if (state.rumble)
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 );
113 return 0;
116 static void draw_axis_view( HDC hdc, RECT rect, SHORT dx, SHORT dy, BOOL set )
118 POINT center =
120 .x = (rect.left + rect.right) / 2,
121 .y = (rect.top + rect.bottom) / 2,
123 POINT pos =
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 )
153 POINT center =
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 );
170 if (y > center.y)
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 );
181 if (y < center.y)
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) );
218 else
220 LOGFONTW logfont =
222 .lfHeight = 16,
223 .lfWeight = FW_NORMAL,
224 .lfCharSet = SYMBOL_CHARSET,
225 .lfFaceName = L"Marlett",
227 WCHAR buffer[4] = {0};
228 HFONT font;
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 );
254 if (msg == WM_PAINT)
256 DWORD index = GetWindowLongW( hwnd, GWLP_USERDATA );
257 UINT axis_size, trigger_size, button_size, horiz_space;
258 struct device_state state;
259 RECT rect, tmp_rect;
260 PAINTSTRUCT paint;
261 HDC hdc;
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;
290 tmp_rect = rect;
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" );
300 rect = tmp_rect;
301 OffsetRect( &rect, 0, rect.bottom - rect.top );
302 tmp_rect = rect;
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" );
313 rect = tmp_rect;
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 );
325 return 0;
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 );
334 HWND parent, view;
335 LONG margin;
336 RECT rect;
338 parent = GetDlgItem( hwnd, IDC_XI_USER_0 + index );
340 GetClientRect( parent, &rect );
341 rect.top += 10;
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;
359 HWND parent;
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 );
372 if (!state.status)
374 HWND view = FindWindowExW( parent, NULL, L"JoyCplXInput", NULL );
375 InvalidateRect( view, NULL, TRUE );
379 static void update_rumble_state( HWND hwnd, DWORD index )
381 HWND parent;
382 LRESULT res;
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 );
398 switch (msg)
400 case WM_INITDIALOG:
401 create_user_view( hwnd, 0 );
402 create_user_view( hwnd, 1 );
403 create_user_view( hwnd, 2 );
404 create_user_view( hwnd, 3 );
405 return TRUE;
407 case WM_COMMAND:
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 );
415 break;
417 return TRUE;
419 case WM_NOTIFY:
420 switch (((NMHDR *)lparam)->code)
422 case PSN_SETACTIVE:
423 dialog_hwnd = hwnd;
424 thread_stop = CreateEventW( NULL, FALSE, FALSE, NULL );
425 thread = CreateThread( NULL, 0, input_thread_proc, (void *)thread_stop, 0, NULL );
426 break;
428 case PSN_RESET:
429 case PSN_KILLACTIVE:
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)
434 MSG msg;
435 while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE ))
437 TranslateMessage( &msg );
438 DispatchMessageW( &msg );
441 CloseHandle( thread_stop );
442 CloseHandle( thread );
443 dialog_hwnd = 0;
444 break;
446 return TRUE;
448 case WM_USER:
449 update_user_view( hwnd, wparam );
450 return TRUE;
453 return FALSE;