setupapi: Add stub keyword to some FIXMEs.
[wine] / dlls / joy.cpl / main.c
1 /*
2  * Joystick testing control panel applet
3  *
4  * Copyright 2012 Lucas Fialho Zawacki
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  */
21
22 #define NONAMELESSUNION
23 #define COBJMACROS
24 #define CONST_VTABLE
25
26 #include <stdarg.h>
27 #include <windef.h>
28 #include <winbase.h>
29 #include <winuser.h>
30 #include <commctrl.h>
31 #include <cpl.h>
32 #include "ole2.h"
33
34 #include "wine/debug.h"
35 #include "wine/unicode.h"
36 #include "joy.h"
37
38 WINE_DEFAULT_DEBUG_CHANNEL(joycpl);
39
40 DECLSPEC_HIDDEN HMODULE hcpl;
41
42 /*********************************************************************
43  *  DllMain
44  */
45 BOOL WINAPI DllMain(HINSTANCE hdll, DWORD reason, LPVOID reserved)
46 {
47     TRACE("(%p, %d, %p)\n", hdll, reason, reserved);
48
49     switch (reason)
50     {
51         case DLL_WINE_PREATTACH:
52             return FALSE;  /* prefer native version */
53
54         case DLL_PROCESS_ATTACH:
55             DisableThreadLibraryCalls(hdll);
56             hcpl = hdll;
57     }
58     return TRUE;
59 }
60
61 /***********************************************************************
62  *  enum_callback [internal]
63  *   Enumerates, creates and sets the common data format for all the joystick devices.
64  *   First time it checks if space for the joysticks was already reserved
65  *   and if not, just counts how many there are.
66  */
67 static BOOL CALLBACK enum_callback(const DIDEVICEINSTANCEW *instance, void *context)
68 {
69     struct JoystickData *data = context;
70     struct Joystick *joystick;
71     DIPROPRANGE proprange;
72     DIDEVCAPS caps;
73
74     if (data->joysticks == NULL)
75     {
76         data->num_joysticks += 1;
77         return DIENUM_CONTINUE;
78     }
79
80     joystick = &data->joysticks[data->cur_joystick];
81     data->cur_joystick += 1;
82
83     IDirectInput8_CreateDevice(data->di, &instance->guidInstance, &joystick->device, NULL);
84     IDirectInputDevice8_SetDataFormat(joystick->device, &c_dfDIJoystick);
85
86     joystick->instance = *instance;
87
88     caps.dwSize = sizeof(caps);
89     IDirectInputDevice8_GetCapabilities(joystick->device, &caps);
90
91     joystick->num_buttons = caps.dwButtons;
92     joystick->num_axes = caps.dwAxes;
93     joystick->forcefeedback = caps.dwFlags & DIDC_FORCEFEEDBACK;
94     joystick->num_effects = 0;
95
96     if (joystick->forcefeedback) data->num_ff++;
97
98     /* Set axis range to ease the GUI visualization */
99     proprange.diph.dwSize = sizeof(DIPROPRANGE);
100     proprange.diph.dwHeaderSize = sizeof(DIPROPHEADER);
101     proprange.diph.dwHow = DIPH_DEVICE;
102     proprange.diph.dwObj = 0;
103     proprange.lMin = TEST_AXIS_MIN;
104     proprange.lMax = TEST_AXIS_MAX;
105
106     IDirectInputDevice_SetProperty(joystick->device, DIPROP_RANGE, &proprange.diph);
107
108     return DIENUM_CONTINUE;
109 }
110
111 /***********************************************************************
112  *  initialize_joysticks [internal]
113  */
114 static void initialize_joysticks(struct JoystickData *data)
115 {
116     data->num_joysticks = 0;
117     data->cur_joystick = 0;
118     IDirectInput8_EnumDevices(data->di, DI8DEVCLASS_GAMECTRL, enum_callback, data, DIEDFL_ATTACHEDONLY);
119     data->joysticks = HeapAlloc(GetProcessHeap(), 0, sizeof(struct Joystick) * data->num_joysticks);
120
121     /* Get all the joysticks */
122     IDirectInput8_EnumDevices(data->di, DI8DEVCLASS_GAMECTRL, enum_callback, data, DIEDFL_ATTACHEDONLY);
123 }
124
125 /***********************************************************************
126  *  destroy_joysticks [internal]
127  */
128 static void destroy_joysticks(struct JoystickData *data)
129 {
130     int i, j;
131
132     for (i = 0; i < data->num_joysticks; i++)
133     {
134
135         if (data->joysticks[i].forcefeedback && data->joysticks[i].num_effects > 0)
136         {
137             for (j = 0; j < data->joysticks[i].num_effects; j++)
138                 IDirectInputEffect_Release(data->joysticks[i].effects[j].effect);
139
140             HeapFree(GetProcessHeap(), 0, data->joysticks[i].effects);
141         }
142
143         IDirectInputDevice8_Unacquire(data->joysticks[i].device);
144         IDirectInputDevice8_Release(data->joysticks[i].device);
145     }
146
147     HeapFree(GetProcessHeap(), 0, data->joysticks);
148 }
149
150 static void initialize_joysticks_list(HWND hwnd, struct JoystickData *data)
151 {
152     int i;
153
154     SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_RESETCONTENT, 0, 0);
155
156     /* Add enumerated joysticks */
157     for (i = 0; i < data->num_joysticks; i++)
158     {
159         struct Joystick *joy = &data->joysticks[i];
160         SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_ADDSTRING, 0, (LPARAM) joy->instance.tszInstanceName);
161     }
162 }
163
164 /******************************************************************************
165  * get_app_key [internal]
166  * Get the default DirectInput key and the selected app config key.
167  */
168 static BOOL get_app_key(HKEY *defkey, HKEY *appkey)
169 {
170     static const WCHAR reg_key[] = { 'S','o','f','t','w','a','r','e','\\',
171                                      'W','i','n','e','\\',
172                                      'D','i','r','e','c','t','I','n','p','u','t','\\',
173                                      'J','o','y','s','t','i','c','k','s','\0' };
174     *appkey = 0;
175
176     /* Registry key can be found in HKCU\Software\Wine\DirectInput */
177     if (RegCreateKeyExW(HKEY_CURRENT_USER, reg_key, 0, NULL, 0, KEY_SET_VALUE | KEY_READ, NULL, defkey, NULL))
178         *defkey = 0;
179
180     return *defkey || *appkey;
181 }
182
183 /******************************************************************************
184  * set_config_key [internal]
185  * Writes a string value to a registry key, deletes the key if value == NULL
186  */
187 static DWORD set_config_key(HKEY defkey, HKEY appkey, const WCHAR *name, const WCHAR *value, DWORD size)
188 {
189     if (value == NULL)
190     {
191         if (appkey && !RegDeleteValueW(appkey, name))
192             return 0;
193
194         if (defkey && !RegDeleteValueW(defkey, name))
195             return 0;
196     }
197     else
198     {
199         if (appkey && !RegSetValueExW(appkey, name, 0, REG_SZ, (const BYTE*) value, (size + 1)*sizeof(WCHAR)))
200             return 0;
201
202         if (defkey && !RegSetValueExW(defkey, name, 0, REG_SZ, (const BYTE*) value, (size + 1)*sizeof(WCHAR)))
203             return 0;
204     }
205
206     return ERROR_FILE_NOT_FOUND;
207 }
208
209 /******************************************************************************
210  * enable_joystick [internal]
211  * Writes to the DirectInput registry key that enables/disables a joystick
212  * from being enumerated.
213  */
214 static void enable_joystick(WCHAR *joy_name, BOOL enable)
215 {
216     static const WCHAR disabled_str[] = {'d','i','s','a','b','l','e','d','\0'};
217     HKEY hkey, appkey;
218
219     get_app_key(&hkey, &appkey);
220
221     if (!enable)
222         set_config_key(hkey, appkey, joy_name, disabled_str, lstrlenW(disabled_str));
223     else
224         set_config_key(hkey, appkey, joy_name, NULL, 0);
225
226     if (hkey) RegCloseKey(hkey);
227     if (appkey) RegCloseKey(appkey);
228 }
229
230 static void initialize_disabled_joysticks_list(HWND hwnd)
231 {
232     static const WCHAR disabled_str[] = {'d','i','s','a','b','l','e','d','\0'};
233     HKEY hkey, appkey;
234     DWORD values = 0;
235     HRESULT hr;
236     DWORD i;
237
238     SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_RESETCONTENT, 0, 0);
239
240     /* Search for disabled joysticks */
241     get_app_key(&hkey, &appkey);
242     RegQueryInfoKeyW(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &values, NULL, NULL, NULL, NULL);
243
244     for (i=0; i < values; i++)
245     {
246         DWORD name_len = MAX_PATH, data_len = MAX_PATH;
247         WCHAR buf_name[MAX_PATH + 9], buf_data[MAX_PATH];
248
249         hr = RegEnumValueW(hkey, i, buf_name, &name_len, NULL, NULL, (BYTE*) buf_data, &data_len);
250
251         if (SUCCEEDED(hr) && !lstrcmpW(disabled_str, buf_data))
252             SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_ADDSTRING, 0, (LPARAM) buf_name);
253     }
254
255     if (hkey) RegCloseKey(hkey);
256     if (appkey) RegCloseKey(appkey);
257 }
258
259 /*********************************************************************
260  * list_dlgproc [internal]
261  *
262  */
263 static INT_PTR CALLBACK list_dlgproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
264 {
265     static struct JoystickData *data;
266     TRACE("(%p, 0x%08x/%d, 0x%lx)\n", hwnd, msg, msg, lparam);
267     switch (msg)
268     {
269         case WM_INITDIALOG:
270         {
271             data = (struct JoystickData*) ((PROPSHEETPAGEW*)lparam)->lParam;
272
273             initialize_joysticks_list(hwnd, data);
274             initialize_disabled_joysticks_list(hwnd);
275
276             EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), FALSE);
277             EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), FALSE);
278
279             /* Store the hwnd to be used with MapDialogRect for unit conversions */
280             data->graphics.hwnd = hwnd;
281
282             return TRUE;
283         }
284
285         case WM_COMMAND:
286
287             switch (LOWORD(wparam))
288             {
289                 case IDC_BUTTONDISABLE:
290                 {
291                     int sel = SendDlgItemMessageW(hwnd, IDC_JOYSTICKLIST, LB_GETCURSEL, 0, 0);
292
293                     if (sel >= 0)
294                     {
295                         enable_joystick(data->joysticks[sel].instance.tszInstanceName, FALSE);
296                         initialize_disabled_joysticks_list(hwnd);
297                     }
298                 }
299                 break;
300
301                 case IDC_BUTTONENABLE:
302                 {
303                     int sel = SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_GETCURSEL, 0, 0);
304
305                     if (sel >= 0)
306                     {
307                         WCHAR text[MAX_PATH];
308                         SendDlgItemMessageW(hwnd, IDC_DISABLEDLIST, LB_GETTEXT, sel, (LPARAM) text);
309                         enable_joystick(text, TRUE);
310                         initialize_disabled_joysticks_list(hwnd);
311                     }
312                 }
313                 break;
314
315                 case IDC_JOYSTICKLIST:
316                     EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), FALSE);
317                     EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), TRUE);
318                 break;
319
320                 case IDC_DISABLEDLIST:
321                     EnableWindow(GetDlgItem(hwnd, IDC_BUTTONENABLE), TRUE);
322                     EnableWindow(GetDlgItem(hwnd, IDC_BUTTONDISABLE), FALSE);
323                 break;
324             }
325
326             return TRUE;
327
328         case WM_NOTIFY:
329             return TRUE;
330
331         default:
332             break;
333     }
334     return FALSE;
335 }
336
337 /*********************************************************************
338  * Joystick testing functions
339  *
340  */
341 static void dump_joy_state(DIJOYSTATE* st, int num_buttons)
342 {
343     int i;
344     TRACE("Ax (% 5d,% 5d,% 5d)\n", st->lX,st->lY, st->lZ);
345     TRACE("RAx (% 5d,% 5d,% 5d)\n", st->lRx, st->lRy, st->lRz);
346     TRACE("Slider (% 5d,% 5d)\n", st->rglSlider[0], st->rglSlider[1]);
347     TRACE("Pov (% 5d,% 5d,% 5d,% 5d)\n", st->rgdwPOV[0], st->rgdwPOV[1], st->rgdwPOV[2], st->rgdwPOV[3]);
348
349     TRACE("Buttons ");
350     for(i=0; i < num_buttons; i++)
351         TRACE("  %c",st->rgbButtons[i] ? 'x' : 'o');
352     TRACE("\n");
353 }
354
355 static void poll_input(const struct Joystick *joy, DIJOYSTATE *state)
356 {
357     HRESULT  hr;
358
359     hr = IDirectInputDevice8_Poll(joy->device);
360
361     /* If it failed, try to acquire the joystick */
362     if (FAILED(hr))
363     {
364         hr = IDirectInputDevice8_Acquire(joy->device);
365         while (hr == DIERR_INPUTLOST) hr = IDirectInputDevice8_Acquire(joy->device);
366     }
367
368     if (hr == DIERR_OTHERAPPHASPRIO) return;
369
370     IDirectInputDevice8_GetDeviceState(joy->device, sizeof(DIJOYSTATE), state);
371 }
372
373 static DWORD WINAPI input_thread(void *param)
374 {
375     int axes_pos[TEST_MAX_AXES][2];
376     DIJOYSTATE state;
377     struct JoystickData *data = param;
378
379     /* Setup POV as clock positions
380      *         0
381      *   31500    4500
382      * 27000  -1    9000
383      *   22500   13500
384      *       18000
385      */
386     int ma = TEST_AXIS_MAX;
387     int pov_val[9] = {0, 4500, 9000, 13500,
388                       18000, 22500, 27000, 31500, -1};
389     int pov_pos[9][2] = { {0, -ma}, {ma/2, -ma/2}, {ma, 0}, {ma/2, ma/2},
390                           {0, ma}, {-ma/2, ma/2}, {-ma, 0}, {-ma/2, -ma/2}, {0, 0} };
391
392     ZeroMemory(&state, sizeof(state));
393
394     while (!data->stop)
395     {
396         int i;
397         unsigned int j;
398
399         poll_input(&data->joysticks[data->chosen_joystick], &state);
400
401         dump_joy_state(&state, data->joysticks[data->chosen_joystick].num_buttons);
402
403         /* Indicate pressed buttons */
404         for (i = 0; i < data->joysticks[data->chosen_joystick].num_buttons; i++)
405             if (state.rgbButtons[i])
406                 SendMessageW(data->graphics.buttons[i], BM_SETSTATE, TRUE, 0);
407
408         /* Indicate axis positions, axes showing are hardcoded for now */
409         axes_pos[0][0] = state.lX;
410         axes_pos[0][1] = state.lY;
411         axes_pos[1][0] = state.lRx;
412         axes_pos[1][1] = state.lRy;
413         axes_pos[2][0] = state.lZ;
414         axes_pos[2][1] = state.lRz;
415
416         /* Set pov values */
417         for (j = 0; i < sizeof(pov_val)/sizeof(pov_val[0]); j++)
418         {
419             if (state.rgdwPOV[0] == pov_val[j])
420             {
421                 axes_pos[3][0] = pov_pos[j][0];
422                 axes_pos[3][1] = pov_pos[j][1];
423             }
424         }
425
426         for (i = 0; i < TEST_MAX_AXES; i++)
427         {
428             RECT r;
429
430             r.left = (TEST_AXIS_X + TEST_NEXT_AXIS_X*i + axes_pos[i][0]);
431             r.top = (TEST_AXIS_Y + axes_pos[i][1]);
432             r.bottom = r.right = 0; /* unused */
433             MapDialogRect(data->graphics.hwnd, &r);
434
435             SetWindowPos(data->graphics.axes[i], 0, r.left, r.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
436         }
437
438         Sleep(TEST_POLL_TIME);
439
440         /* Reset button state */
441         for (i = 0; i < data->joysticks[data->chosen_joystick].num_buttons; i++)
442             SendMessageW(data->graphics.buttons[i], BM_SETSTATE, FALSE, 0);
443     }
444
445     return 0;
446 }
447
448 static void test_handle_joychange(HWND hwnd, struct JoystickData *data)
449 {
450     int i;
451
452     if (data->num_joysticks == 0) return;
453
454     data->chosen_joystick = SendDlgItemMessageW(hwnd, IDC_TESTSELECTCOMBO, CB_GETCURSEL, 0, 0);
455
456     /* Enable only  buttons present in the device */
457     for (i = 0; i < TEST_MAX_BUTTONS; i++)
458         ShowWindow(data->graphics.buttons[i], i <= data->joysticks[data->chosen_joystick].num_buttons);
459 }
460
461 /*********************************************************************
462  * button_number_to_wchar [internal]
463  *  Transforms an integer in the interval [0,99] into a 2 character WCHAR string
464  */
465 static void button_number_to_wchar(int n, WCHAR str[3])
466 {
467     str[1] = n % 10 + '0';
468     n /= 10;
469     str[0] = n % 10 + '0';
470     str[2] = '\0';
471 }
472
473 static void draw_joystick_buttons(HWND hwnd, struct JoystickData* data)
474 {
475     int i;
476     int row = 0, col = 0;
477     WCHAR button_label[3];
478     HINSTANCE hinst = (HINSTANCE) GetWindowLongPtrW(hwnd, GWLP_HINSTANCE);
479     static WCHAR button_class[] = {'B','u','t','t','o','n','\0'};
480
481     for (i = 0; i < TEST_MAX_BUTTONS; i++)
482     {
483         RECT r;
484
485         if ((i % TEST_BUTTON_COL_MAX) == 0 && i != 0)
486         {
487             row += 1;
488             col = 0;
489         }
490
491         r.left = (TEST_BUTTON_X + TEST_NEXT_BUTTON_X*col);
492         r.top = (TEST_BUTTON_Y + TEST_NEXT_BUTTON_Y*row);
493         r.right = r.left + TEST_BUTTON_SIZE_X;
494         r.bottom = r.top + TEST_BUTTON_SIZE_Y;
495         MapDialogRect(hwnd, &r);
496
497         button_number_to_wchar(i + 1, button_label);
498
499         data->graphics.buttons[i] = CreateWindowW(button_class, button_label, WS_CHILD,
500             r.left, r.top, r.right - r.left, r.bottom - r.top,
501             hwnd, NULL, NULL, hinst);
502
503         col += 1;
504     }
505 }
506
507 static void draw_joystick_axes(HWND hwnd, struct JoystickData* data)
508 {
509     int i;
510     HINSTANCE hinst = (HINSTANCE) GetWindowLongPtrW(hwnd, GWLP_HINSTANCE);
511     static const WCHAR button_class[] = {'B','u','t','t','o','n','\0'};
512     static const WCHAR axes_names[TEST_MAX_AXES][7] = { {'X',',','Y','\0'}, {'R','x',',','R','y','\0'},
513                                                         {'Z',',','R','z','\0'}, {'P','O','V','\0'} };
514     static const DWORD axes_idc[TEST_MAX_AXES] = { IDC_TESTGROUPXY, IDC_TESTGROUPRXRY,
515                                                    IDC_TESTGROUPZRZ, IDC_TESTGROUPPOV };
516
517     for (i = 0; i < TEST_MAX_AXES; i++)
518     {
519         RECT r;
520         /* Set axis box name */
521         SetWindowTextW(GetDlgItem(hwnd, axes_idc[i]), axes_names[i]);
522
523         r.left = (TEST_AXIS_X + TEST_NEXT_AXIS_X*i);
524         r.top = TEST_AXIS_Y;
525         r.right = r.left + TEST_AXIS_SIZE_X;
526         r.bottom = r.top + TEST_AXIS_SIZE_Y;
527         MapDialogRect(hwnd, &r);
528
529         data->graphics.axes[i] = CreateWindowW( button_class, NULL, WS_CHILD | WS_VISIBLE,
530             r.left, r.top, r.right - r.left, r.bottom - r.top,
531             hwnd, NULL, NULL, hinst);
532     }
533 }
534
535 /*********************************************************************
536  * test_dlgproc [internal]
537  *
538  */
539 static INT_PTR CALLBACK test_dlgproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
540 {
541     static HANDLE thread;
542     static struct JoystickData *data;
543     TRACE("(%p, 0x%08x/%d, 0x%lx)\n", hwnd, msg, msg, lparam);
544
545     switch (msg)
546     {
547         case WM_INITDIALOG:
548         {
549             int i;
550
551             data = (struct JoystickData*) ((PROPSHEETPAGEW*)lparam)->lParam;
552
553             /* Add enumerated joysticks to the combobox */
554             for (i = 0; i < data->num_joysticks; i++)
555             {
556                 struct Joystick *joy = &data->joysticks[i];
557                 SendDlgItemMessageW(hwnd, IDC_TESTSELECTCOMBO, CB_ADDSTRING, 0, (LPARAM) joy->instance.tszInstanceName);
558             }
559
560             draw_joystick_buttons(hwnd, data);
561             draw_joystick_axes(hwnd, data);
562
563             return TRUE;
564         }
565
566         case WM_COMMAND:
567             switch(wparam)
568             {
569                 case MAKEWPARAM(IDC_TESTSELECTCOMBO, CBN_SELCHANGE):
570                     test_handle_joychange(hwnd, data);
571                 break;
572             }
573             return TRUE;
574
575         case WM_NOTIFY:
576             switch(((LPNMHDR)lparam)->code)
577             {
578                 case PSN_SETACTIVE:
579                 {
580                     DWORD tid;
581
582                     /* Initialize input thread */
583                     if (data->num_joysticks > 0)
584                     {
585                         data->stop = FALSE;
586
587                         /* Set the first joystick as default */
588                         SendDlgItemMessageW(hwnd, IDC_TESTSELECTCOMBO, CB_SETCURSEL, 0, 0);
589                         test_handle_joychange(hwnd, data);
590
591                         thread = CreateThread(NULL, 0, input_thread, (void*) data, 0, &tid);
592                     }
593                 }
594                 break;
595
596                 case PSN_RESET: /* intentional fall-through */
597                 case PSN_KILLACTIVE:
598                     /* Stop input thread */
599                     data->stop = TRUE;
600                     MsgWaitForMultipleObjects(1, &thread, FALSE, INFINITE, 0);
601                     CloseHandle(thread);
602                 break;
603             }
604             return TRUE;
605     }
606     return FALSE;
607 }
608
609 /*********************************************************************
610  * Joystick force feedback testing functions
611  *
612  */
613 static void draw_ff_axis(HWND hwnd, struct JoystickData *data)
614 {
615     HINSTANCE hinst = (HINSTANCE) GetWindowLongPtrW(hwnd, GWLP_HINSTANCE);
616     static WCHAR button_class[] = {'B','u','t','t','o','n','\0'};
617     RECT r;
618
619     r.left = FF_AXIS_X;
620     r.top = FF_AXIS_Y;
621     r.right = r.left + FF_AXIS_SIZE_X;
622     r.bottom = r.top + FF_AXIS_SIZE_Y;
623     MapDialogRect(hwnd, &r);
624
625     /* Draw direction axis */
626     data->graphics.ff_axis = CreateWindowW( button_class, NULL, WS_CHILD | WS_VISIBLE,
627         r.left, r.top, r.right - r.left, r.bottom - r.top,
628         hwnd, NULL, NULL, hinst);
629 }
630
631 static void initialize_effects_list(HWND hwnd, struct Joystick* joy)
632 {
633     int i;
634
635     SendDlgItemMessageW(hwnd, IDC_FFEFFECTLIST, LB_RESETCONTENT, 0, 0);
636
637     for (i=0; i < joy->num_effects; i++)
638     {
639         /* Effect names start with GUID_, so we'll skip this part */
640         WCHAR *name = joy->effects[i].info.tszName + 5;
641         SendDlgItemMessageW(hwnd, IDC_FFEFFECTLIST, LB_ADDSTRING, 0, (LPARAM) name);
642     }
643 }
644
645 static void ff_handle_joychange(HWND hwnd, struct JoystickData *data)
646 {
647     int sel;
648
649     if (data->num_ff == 0) return;
650
651     sel = SendDlgItemMessageW(hwnd, IDC_FFSELECTCOMBO, CB_GETCURSEL, 0, 0);
652     data->chosen_joystick = SendDlgItemMessageW(hwnd, IDC_FFSELECTCOMBO, CB_GETITEMDATA, sel, 0);
653     initialize_effects_list(hwnd, &data->joysticks[data->chosen_joystick]);
654 }
655
656 static void ff_handle_effectchange(HWND hwnd, struct Joystick *joy)
657 {
658     int sel = SendDlgItemMessageW(hwnd, IDC_FFEFFECTLIST, LB_GETCURSEL, 0, 0);
659
660     if (sel < 0) return;
661
662     joy->chosen_effect = sel;
663 }
664
665 static DWORD WINAPI ff_input_thread(void *param)
666 {
667     struct JoystickData *data = param;
668     DIJOYSTATE state;
669
670     ZeroMemory(&state, sizeof(state));
671
672     while (!data->stop)
673     {
674         int i;
675         struct Joystick *joy = &data->joysticks[data->chosen_joystick];
676         int chosen_effect = joy->chosen_effect;
677         DIEFFECT *dieffect;
678         DWORD flags = DIEP_AXES | DIEP_DIRECTION | DIEP_NORESTART;
679         RECT r;
680
681         /* Skip this if we have no effects */
682         if (joy->num_effects == 0 || chosen_effect < 0) continue;
683
684         poll_input(joy, &state);
685
686         /* Set ff parameters and draw the axis */
687         dieffect = &joy->effects[chosen_effect].params;
688         dieffect->rgdwAxes[0] = state.lX;
689         dieffect->rgdwAxes[1] = state.lY;
690
691         r.left = FF_AXIS_X + state.lX;
692         r.top = FF_AXIS_Y + state.lY;
693         r.right = r.bottom = 0; /* unused */
694         MapDialogRect(data->graphics.hwnd, &r);
695
696         SetWindowPos(data->graphics.ff_axis, 0, r.left, r.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
697
698         for (i=0; i < joy->num_buttons; i++)
699             if (state.rgbButtons[i])
700             {
701                 IDirectInputEffect_SetParameters(joy->effects[chosen_effect].effect, dieffect, flags);
702                 IDirectInputEffect_Start(joy->effects[chosen_effect].effect, 1, 0);
703                 break;
704             }
705
706         Sleep(TEST_POLL_TIME);
707     }
708
709     return 0;
710 }
711
712 /***********************************************************************
713  *  ff_effects_callback [internal]
714  *   Enumerates, creates, sets the some parameters and stores all ff effects
715  *   supported by the joystick. Works like enum_callback, counting the effects
716  *   first and then storing them.
717  */
718 static BOOL CALLBACK ff_effects_callback(const DIEFFECTINFOW *pdei, void *pvRef)
719 {
720     HRESULT hr;
721     DIEFFECT dieffect;
722     DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
723     int direction[2] = {0, 0};
724     struct Joystick *joystick = pvRef;
725
726     if (joystick->effects == NULL)
727     {
728         joystick->num_effects += 1;
729         return DIENUM_CONTINUE;
730     }
731
732     hr = IDirectInputDevice8_Acquire(joystick->device);
733
734     if (FAILED(hr)) return DIENUM_CONTINUE;
735
736     ZeroMemory(&dieffect, sizeof(dieffect));
737
738     dieffect.dwSize = sizeof(dieffect);
739     dieffect.dwFlags = DIEFF_CARTESIAN;
740     dieffect.dwDuration = FF_PLAY_TIME;
741
742     dieffect.cAxes = 2;
743     dieffect.rgdwAxes = axes;
744     dieffect.rglDirection = direction;
745
746     if (IsEqualGUID(&pdei->guid, &GUID_RampForce))
747     {
748         DIRAMPFORCE rforce;
749
750         rforce.lStart = 0;
751         rforce.lEnd = DI_FFNOMINALMAX;
752
753         dieffect.cbTypeSpecificParams = sizeof(rforce);
754         dieffect.lpvTypeSpecificParams = &rforce;
755         dieffect.dwFlags |= DIEP_TYPESPECIFICPARAMS;
756     }
757     else if (IsEqualGUID(&pdei->guid, &GUID_ConstantForce))
758     {
759         DICONSTANTFORCE cforce;
760
761         cforce.lMagnitude = DI_FFNOMINALMAX;
762
763         dieffect.cbTypeSpecificParams = sizeof(cforce);
764         dieffect.lpvTypeSpecificParams = &cforce;
765         dieffect.dwFlags |= DIEP_TYPESPECIFICPARAMS;
766     }
767     else if (IsEqualGUID(&pdei->guid, &GUID_Sine) ||
768              IsEqualGUID(&pdei->guid, &GUID_Square) ||
769              IsEqualGUID(&pdei->guid, &GUID_Triangle) ||
770              IsEqualGUID(&pdei->guid, &GUID_SawtoothUp) ||
771              IsEqualGUID(&pdei->guid, &GUID_SawtoothDown))
772     {
773         DIPERIODIC pforce;
774
775         pforce.dwMagnitude = DI_FFNOMINALMAX;
776         pforce.lOffset = 0;
777         pforce.dwPhase = 0;
778         pforce.dwPeriod = FF_PERIOD_TIME;
779
780         dieffect.cbTypeSpecificParams = sizeof(pforce);
781         dieffect.lpvTypeSpecificParams = &pforce;
782         dieffect.dwFlags |= DIEP_TYPESPECIFICPARAMS;
783     }
784
785     hr = IDirectInputDevice2_CreateEffect(
786         joystick->device, &pdei->guid, &dieffect, &joystick->effects[joystick->cur_effect].effect, NULL);
787
788     joystick->effects[joystick->cur_effect].params = dieffect;
789     joystick->effects[joystick->cur_effect].info = *pdei;
790     joystick->cur_effect += 1;
791
792     return DIENUM_CONTINUE;
793 }
794
795 /*********************************************************************
796  * ff_dlgproc [internal]
797  *
798  */
799 static INT_PTR CALLBACK ff_dlgproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
800 {
801     static HANDLE thread;
802     static struct JoystickData *data;
803     TRACE("(%p, 0x%08x/%d, 0x%lx)\n", hwnd, msg, msg, lparam);
804
805     switch (msg)
806     {
807         case WM_INITDIALOG:
808         {
809             int i, cur = 0;
810
811             data = (struct JoystickData*) ((PROPSHEETPAGEW*)lparam)->lParam;
812
813             /* Add joysticks with FF support to the combobox and get the effects */
814             for (i = 0; i < data->num_joysticks; i++)
815             {
816                 struct Joystick *joy = &data->joysticks[i];
817
818                 if (joy->forcefeedback)
819                 {
820                     SendDlgItemMessageW(hwnd, IDC_FFSELECTCOMBO, CB_ADDSTRING, 0, (LPARAM) joy->instance.tszInstanceName);
821                     SendDlgItemMessageW(hwnd, IDC_FFSELECTCOMBO, CB_SETITEMDATA, cur, i);
822
823                     cur++;
824
825                     /* Count device effects and then store them */
826                     joy->num_effects = 0;
827                     joy->effects = NULL;
828                     IDirectInputDevice8_EnumEffects(joy->device, ff_effects_callback, (void *) joy, 0);
829                     joy->effects = HeapAlloc(GetProcessHeap(), 0, sizeof(struct Effect) * joy->num_effects);
830
831                     joy->cur_effect = 0;
832                     IDirectInputDevice8_EnumEffects(joy->device, ff_effects_callback, (void*) joy, 0);
833                     joy->num_effects = joy->cur_effect;
834                 }
835             }
836
837             draw_ff_axis(hwnd, data);
838
839             return TRUE;
840         }
841
842         case WM_COMMAND:
843             switch(wparam)
844             {
845                 case MAKEWPARAM(IDC_FFSELECTCOMBO, CBN_SELCHANGE):
846                     ff_handle_joychange(hwnd, data);
847
848                     SendDlgItemMessageW(hwnd, IDC_FFEFFECTLIST, LB_SETCURSEL, 0, 0);
849                     ff_handle_effectchange(hwnd, &data->joysticks[data->chosen_joystick]);
850                 break;
851
852                 case MAKEWPARAM(IDC_FFEFFECTLIST, LBN_SELCHANGE):
853                     ff_handle_effectchange(hwnd, &data->joysticks[data->chosen_joystick]);
854                 break;
855             }
856             return TRUE;
857
858         case WM_NOTIFY:
859             switch(((LPNMHDR)lparam)->code)
860             {
861                 case PSN_SETACTIVE:
862                     if (data->num_ff > 0)
863                     {
864                         DWORD tid;
865
866                         data->stop = FALSE;
867                         /* Set the first joystick as default */
868                         SendDlgItemMessageW(hwnd, IDC_FFSELECTCOMBO, CB_SETCURSEL, 0, 0);
869                         ff_handle_joychange(hwnd, data);
870
871                         SendDlgItemMessageW(hwnd, IDC_FFEFFECTLIST, LB_SETCURSEL, 0, 0);
872                         ff_handle_effectchange(hwnd, &data->joysticks[data->chosen_joystick]);
873
874                         thread = CreateThread(NULL, 0, ff_input_thread, (void*) data, 0, &tid);
875                     }
876                 break;
877
878                 case PSN_RESET: /* intentional fall-through */
879                 case PSN_KILLACTIVE:
880                     /* Stop ff thread */
881                     data->stop = TRUE;
882                     MsgWaitForMultipleObjects(1, &thread, FALSE, INFINITE, 0);
883                     CloseHandle(thread);
884                 break;
885             }
886             return TRUE;
887     }
888     return FALSE;
889 }
890
891 /******************************************************************************
892  * propsheet_callback [internal]
893  */
894 static int CALLBACK propsheet_callback(HWND hwnd, UINT msg, LPARAM lparam)
895 {
896     TRACE("(%p, 0x%08x/%d, 0x%lx)\n", hwnd, msg, msg, lparam);
897     switch (msg)
898     {
899         case PSCB_INITIALIZED:
900             break;
901     }
902     return 0;
903 }
904
905 /******************************************************************************
906  * display_cpl_sheets [internal]
907  *
908  * Build and display the dialog with all control panel propertysheets
909  *
910  */
911 static void display_cpl_sheets(HWND parent, struct JoystickData *data)
912 {
913     INITCOMMONCONTROLSEX icex;
914     PROPSHEETPAGEW psp[NUM_PROPERTY_PAGES];
915     PROPSHEETHEADERW psh;
916     DWORD id = 0;
917
918     OleInitialize(NULL);
919     /* Initialize common controls */
920     icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
921     icex.dwICC = ICC_LISTVIEW_CLASSES | ICC_BAR_CLASSES;
922     InitCommonControlsEx(&icex);
923
924     ZeroMemory(&psh, sizeof(psh));
925     ZeroMemory(psp, sizeof(psp));
926
927     /* Fill out all PROPSHEETPAGE */
928     psp[id].dwSize = sizeof (PROPSHEETPAGEW);
929     psp[id].hInstance = hcpl;
930     psp[id].u.pszTemplate = MAKEINTRESOURCEW(IDD_LIST);
931     psp[id].pfnDlgProc = list_dlgproc;
932     psp[id].lParam = (INT_PTR) data;
933     id++;
934
935     psp[id].dwSize = sizeof (PROPSHEETPAGEW);
936     psp[id].hInstance = hcpl;
937     psp[id].u.pszTemplate = MAKEINTRESOURCEW(IDD_TEST);
938     psp[id].pfnDlgProc = test_dlgproc;
939     psp[id].lParam = (INT_PTR) data;
940     id++;
941
942     psp[id].dwSize = sizeof (PROPSHEETPAGEW);
943     psp[id].hInstance = hcpl;
944     psp[id].u.pszTemplate = MAKEINTRESOURCEW(IDD_FORCEFEEDBACK);
945     psp[id].pfnDlgProc = ff_dlgproc;
946     psp[id].lParam = (INT_PTR) data;
947     id++;
948
949     /* Fill out the PROPSHEETHEADER */
950     psh.dwSize = sizeof (PROPSHEETHEADERW);
951     psh.dwFlags = PSH_PROPSHEETPAGE | PSH_USEICONID | PSH_USECALLBACK;
952     psh.hwndParent = parent;
953     psh.hInstance = hcpl;
954     psh.pszCaption = MAKEINTRESOURCEW(IDS_CPL_NAME);
955     psh.nPages = id;
956     psh.u3.ppsp = psp;
957     psh.pfnCallback = propsheet_callback;
958
959     /* display the dialog */
960     PropertySheetW(&psh);
961
962     OleUninitialize();
963 }
964
965 /*********************************************************************
966  * CPlApplet (joy.cpl.@)
967  *
968  * Control Panel entry point
969  *
970  * PARAMS
971  *  hWnd    [I] Handle for the Control Panel Window
972  *  command [I] CPL_* Command
973  *  lParam1 [I] first extra Parameter
974  *  lParam2 [I] second extra Parameter
975  *
976  * RETURNS
977  *  Depends on the command
978  *
979  */
980 LONG CALLBACK CPlApplet(HWND hwnd, UINT command, LPARAM lParam1, LPARAM lParam2)
981 {
982     static struct JoystickData data;
983     TRACE("(%p, %u, 0x%lx, 0x%lx)\n", hwnd, command, lParam1, lParam2);
984
985     switch (command)
986     {
987         case CPL_INIT:
988         {
989             HRESULT hr;
990
991             /* Initialize dinput */
992             hr = DirectInput8Create(GetModuleHandleW(NULL), DIRECTINPUT_VERSION, &IID_IDirectInput8W, (void**)&data.di, NULL);
993
994             if (FAILED(hr))
995             {
996                 ERR("Failed to initialize DirectInput: 0x%08x\n", hr);
997                 return FALSE;
998             }
999
1000             /* Then get all the connected joysticks */
1001             initialize_joysticks(&data);
1002
1003             return TRUE;
1004         }
1005         case CPL_GETCOUNT:
1006             return 1;
1007
1008         case CPL_INQUIRE:
1009         {
1010             CPLINFO *appletInfo = (CPLINFO *) lParam2;
1011
1012             appletInfo->idName = IDS_CPL_NAME;
1013             appletInfo->idInfo = IDS_CPL_INFO;
1014             appletInfo->lData = 0;
1015             return TRUE;
1016         }
1017
1018         case CPL_DBLCLK:
1019             display_cpl_sheets(hwnd, &data);
1020             break;
1021
1022         case CPL_STOP:
1023             destroy_joysticks(&data);
1024
1025             /* And destroy dinput too */
1026             IDirectInput8_Release(data.di);
1027             break;
1028     }
1029
1030     return FALSE;
1031 }