xref: /reactos/dll/directx/wine/dinput/config.c (revision 53221834)
1 /*
2  * Copyright (c) 2011 Lucas Fialho Zawacki
3  *
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.
8  *
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.
13  *
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
17  */
18 
19 #define NONAMELESSUNION
20 
21 
22 #include "wine/unicode.h"
23 #include "objbase.h"
24 #include "dinput_private.h"
25 #include "device_private.h"
26 #include "resource.h"
27 
28 #include "wine/heap.h"
29 
30 typedef struct {
31     int nobjects;
32     IDirectInputDevice8W *lpdid;
33     DIDEVICEINSTANCEW ddi;
34     DIDEVICEOBJECTINSTANCEW ddo[256];
35     /* ActionFormat for every user.
36      * In same order as ConfigureDevicesData usernames */
37     DIACTIONFORMATW *user_afs;
38 } DeviceData;
39 
40 typedef struct {
41     int ndevices;
42     DeviceData *devices;
43 } DIDevicesData;
44 
45 typedef struct {
46     IDirectInput8W *lpDI;
47     LPDIACTIONFORMATW original_lpdiaf;
48     DIDevicesData devices_data;
49     int display_only;
50     int nusernames;
51     WCHAR **usernames;
52 } ConfigureDevicesData;
53 
54 /*
55  * Enumeration callback functions
56  */
57 static BOOL CALLBACK collect_objects(LPCDIDEVICEOBJECTINSTANCEW lpddo, LPVOID pvRef)
58 {
59     DeviceData *data = (DeviceData*) pvRef;
60 
61     data->ddo[data->nobjects] = *lpddo;
62 
63     data->nobjects++;
64     return DIENUM_CONTINUE;
65 }
66 
67 static BOOL CALLBACK collect_devices(LPCDIDEVICEINSTANCEW lpddi, IDirectInputDevice8W *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef)
68 {
69     ConfigureDevicesData *data = (ConfigureDevicesData*) pvRef;
70     DeviceData *device;
71     int i, j;
72 
73     IDirectInputDevice_AddRef(lpdid);
74 
75     /* alloc array for devices if this is our first device */
76     if (!data->devices_data.ndevices)
77         data->devices_data.devices = HeapAlloc(GetProcessHeap(), 0, sizeof(DeviceData) * (dwRemaining + 1));
78     device = &data->devices_data.devices[data->devices_data.ndevices];
79     device->lpdid = lpdid;
80     device->ddi = *lpddi;
81 
82     device->nobjects = 0;
83     IDirectInputDevice_EnumObjects(lpdid, collect_objects, (LPVOID) device, DIDFT_ALL);
84 
85     device->user_afs = heap_alloc(sizeof(*device->user_afs) * data->nusernames);
86     memset(device->user_afs, 0, sizeof(*device->user_afs) * data->nusernames);
87     for (i = 0; i < data->nusernames; i++)
88     {
89         DIACTIONFORMATW *user_af = &device->user_afs[i];
90         user_af->dwNumActions = data->original_lpdiaf->dwNumActions;
91         user_af->guidActionMap = data->original_lpdiaf->guidActionMap;
92         user_af->rgoAction = heap_alloc(sizeof(DIACTIONW) * data->original_lpdiaf->dwNumActions);
93         memset(user_af->rgoAction, 0, sizeof(DIACTIONW) * data->original_lpdiaf->dwNumActions);
94         for (j = 0; j < user_af->dwNumActions; j++)
95         {
96             user_af->rgoAction[j].dwSemantic = data->original_lpdiaf->rgoAction[j].dwSemantic;
97             user_af->rgoAction[j].dwFlags = data->original_lpdiaf->rgoAction[j].dwFlags;
98             user_af->rgoAction[j].u.lptszActionName = data->original_lpdiaf->rgoAction[j].u.lptszActionName;
99         }
100         IDirectInputDevice8_BuildActionMap(lpdid, user_af, data->usernames[i], 0);
101     }
102 
103     data->devices_data.ndevices++;
104     return DIENUM_CONTINUE;
105 }
106 
107 /*
108  * Listview utility functions
109  */
110 static void init_listview_columns(HWND dialog)
111 {
112     LVCOLUMNW listColumn;
113     RECT viewRect;
114     int width;
115     WCHAR column[MAX_PATH];
116 
117     GetClientRect(GetDlgItem(dialog, IDC_DEVICEOBJECTSLIST), &viewRect);
118     width = (viewRect.right - viewRect.left)/2;
119 
120     LoadStringW(DINPUT_instance, IDS_OBJECTCOLUMN, column, ARRAY_SIZE(column));
121     listColumn.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
122     listColumn.pszText = column;
123     listColumn.cchTextMax = lstrlenW(listColumn.pszText);
124     listColumn.cx = width;
125 
126     SendDlgItemMessageW (dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTCOLUMNW, 0, (LPARAM) &listColumn);
127 
128     LoadStringW(DINPUT_instance, IDS_ACTIONCOLUMN, column, ARRAY_SIZE(column));
129     listColumn.cx = width;
130     listColumn.pszText = column;
131     listColumn.cchTextMax = lstrlenW(listColumn.pszText);
132 
133     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTCOLUMNW, 1, (LPARAM) &listColumn);
134 }
135 
136 static int lv_get_cur_item(HWND dialog)
137 {
138     return SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
139 }
140 
141 static int lv_get_item_data(HWND dialog, int index)
142 {
143     LVITEMW item;
144 
145     if (index < 0) return -1;
146 
147     item.mask = LVIF_PARAM;
148     item.iItem = index;
149     item.iSubItem = 0;
150 
151     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_GETITEMW , 0, (LPARAM)&item);
152 
153     return item.lParam;
154 }
155 
156 static void lv_set_action(HWND dialog, int item, int action, LPDIACTIONFORMATW lpdiaf)
157 {
158     static const WCHAR no_action[] = {'-','\0'};
159     const WCHAR *action_text = no_action;
160     LVITEMW lvItem;
161 
162     if (item < 0) return;
163 
164     if (action != -1)
165         action_text = lpdiaf->rgoAction[action].u.lptszActionName;
166 
167     /* Keep the action and text in the listview item */
168     lvItem.iItem = item;
169 
170     lvItem.mask = LVIF_PARAM;
171     lvItem.iSubItem = 0;
172     lvItem.lParam = (LPARAM) action;
173 
174     /* Action index */
175     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_SETITEMW, 0, (LPARAM) &lvItem);
176 
177     lvItem.mask = LVIF_TEXT;
178     lvItem.iSubItem = 1;
179     lvItem.pszText = (WCHAR *)action_text;
180     lvItem.cchTextMax = lstrlenW(lvItem.pszText);
181 
182     /* Text */
183     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_SETITEMW, 0, (LPARAM) &lvItem);
184 }
185 
186 /*
187  * Utility functions
188  */
189 static DeviceData* get_cur_device(HWND dialog)
190 {
191     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
192     int sel = SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_GETCURSEL, 0, 0);
193     return &data->devices_data.devices[sel];
194 }
195 
196 static DIACTIONFORMATW *get_cur_lpdiaf(HWND dialog)
197 {
198     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
199     int controller_sel = SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_GETCURSEL, 0, 0);
200     int player_sel = SendDlgItemMessageW(dialog, IDC_PLAYERCOMBO, CB_GETCURSEL, 0, 0);
201     return &data->devices_data.devices[controller_sel].user_afs[player_sel];
202 }
203 
204 static DIACTIONFORMATW *get_original_lpdiaf(HWND dialog)
205 {
206     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
207     return data->original_lpdiaf;
208 }
209 
210 static int dialog_display_only(HWND dialog)
211 {
212     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
213     return data->display_only;
214 }
215 
216 static void init_devices(HWND dialog, ConfigureDevicesData *data)
217 {
218     int i;
219 
220     /* Collect and insert */
221     data->devices_data.ndevices = 0;
222     IDirectInput8_EnumDevicesBySemantics(data->lpDI, NULL, data->original_lpdiaf, collect_devices, (LPVOID) data, 0);
223 
224     for (i = 0; i < data->devices_data.ndevices; i++)
225         SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_ADDSTRING, 0, (LPARAM) data->devices_data.devices[i].ddi.tszProductName );
226     for (i = 0; i < data->nusernames; i++)
227         SendDlgItemMessageW(dialog, IDC_PLAYERCOMBO, CB_ADDSTRING, 0, (LPARAM) data->usernames[i]);
228 }
229 
230 static void destroy_data(HWND dialog)
231 {
232     int i, j;
233     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
234     DIDevicesData *devices_data = &data->devices_data;
235 
236     /* Free the devices */
237     for (i=0; i < devices_data->ndevices; i++)
238     {
239         IDirectInputDevice8_Release(devices_data->devices[i].lpdid);
240         for (j=0; j < data->nusernames; j++)
241             heap_free(devices_data->devices[i].user_afs[j].rgoAction);
242         heap_free(devices_data->devices[i].user_afs);
243     }
244 
245     HeapFree(GetProcessHeap(), 0, devices_data->devices);
246 }
247 
248 static void fill_device_object_list(HWND dialog)
249 {
250     DeviceData *device = get_cur_device(dialog);
251     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
252     LVITEMW item;
253     int i, j;
254 
255     /* Clean the listview */
256     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_DELETEALLITEMS, 0, 0);
257 
258     /* Add each object */
259     for (i=0; i < device->nobjects; i++)
260     {
261         DWORD ddo_inst, ddo_type;
262         int action = -1;
263 
264         item.mask = LVIF_TEXT | LVIF_PARAM;
265         item.iItem = i;
266         item.iSubItem = 0;
267         item.pszText = device->ddo[i].tszName;
268         item.cchTextMax = lstrlenW(item.pszText);
269 
270         /* Add the item */
271         SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTITEMW, 0, (LPARAM) &item);
272         ddo_inst = DIDFT_GETINSTANCE(device->ddo[i].dwType);
273         ddo_type = DIDFT_GETTYPE(device->ddo[i].dwType);
274 
275         /* Search for an assigned action for this device */
276         for (j=0; j < lpdiaf->dwNumActions; j++)
277         {
278             DWORD af_inst = DIDFT_GETINSTANCE(lpdiaf->rgoAction[j].dwObjID);
279             DWORD af_type = DIDFT_GETTYPE(lpdiaf->rgoAction[j].dwObjID);
280             if (af_type == DIDFT_PSHBUTTON) af_type = DIDFT_BUTTON;
281             if (af_type == DIDFT_RELAXIS) af_type = DIDFT_AXIS;
282             /* NOTE previously compared dwType == dwObjId but default buildActionMap actions
283              * were PSHBUTTON and RELAXS and didnt show up on config */
284             if (IsEqualGUID(&lpdiaf->rgoAction[j].guidInstance, &device->ddi.guidInstance) &&
285                 ddo_inst == af_inst && ddo_type & af_type)
286             {
287                 action = j;
288                 break;
289             }
290         }
291 
292         lv_set_action(dialog, i, action, lpdiaf);
293     }
294 }
295 
296 static void show_suitable_actions(HWND dialog)
297 {
298     DeviceData *device = get_cur_device(dialog);
299     LPDIACTIONFORMATW lpdiaf = get_original_lpdiaf(dialog);
300     int i, added = 0;
301     int obj = lv_get_cur_item(dialog);
302 
303     if (obj < 0) return;
304 
305     SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_RESETCONTENT, 0, 0);
306 
307     for (i=0; i < lpdiaf->dwNumActions; i++)
308     {
309         /* Skip keyboard actions for non keyboards */
310         if (GET_DIDEVICE_TYPE(device->ddi.dwDevType) != DI8DEVTYPE_KEYBOARD &&
311             (lpdiaf->rgoAction[i].dwSemantic & DIKEYBOARD_MASK) == DIKEYBOARD_MASK) continue;
312 
313         /* Skip mouse actions for non mouses */
314         if (GET_DIDEVICE_TYPE(device->ddi.dwDevType) != DI8DEVTYPE_MOUSE &&
315             (lpdiaf->rgoAction[i].dwSemantic & DIMOUSE_MASK) == DIMOUSE_MASK) continue;
316 
317         /* Add action string and index in the action format to the list entry */
318         if (DIDFT_GETINSTANCE(lpdiaf->rgoAction[i].dwSemantic) & DIDFT_GETTYPE(device->ddo[obj].dwType))
319         {
320             SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_ADDSTRING, 0, (LPARAM)lpdiaf->rgoAction[i].u.lptszActionName);
321             SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_SETITEMDATA, added, (LPARAM) i);
322             added++;
323         }
324     }
325 }
326 
327 static void assign_action(HWND dialog)
328 {
329     DeviceData *device = get_cur_device(dialog);
330     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
331     LVFINDINFOW lvFind;
332     int sel = SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_GETCURSEL, 0, 0);
333     int action = SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_GETITEMDATA, sel, 0);
334     int obj = lv_get_cur_item(dialog);
335     int old_action = lv_get_item_data(dialog, obj);
336     int used_obj;
337     DWORD type;
338 
339     if (old_action == action) return;
340     if (obj < 0) return;
341     if (lpdiaf->rgoAction[old_action].dwFlags & DIA_APPFIXED) return;
342 
343     type = device->ddo[obj].dwType;
344 
345     /* Clear old action */
346     if (old_action != -1)
347     {
348         lpdiaf->rgoAction[old_action].dwObjID = 0;
349         lpdiaf->rgoAction[old_action].guidInstance = GUID_NULL;
350         lpdiaf->rgoAction[old_action].dwHow = DIAH_UNMAPPED;
351     }
352 
353     /* Find if action text is already set for other object and unset it */
354     lvFind.flags = LVFI_PARAM;
355     lvFind.lParam = action;
356 
357     used_obj = SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_FINDITEMW, -1, (LPARAM) &lvFind);
358 
359     lv_set_action(dialog, used_obj, -1, lpdiaf);
360 
361     /* Set new action */
362     lpdiaf->rgoAction[action].dwObjID = type;
363     lpdiaf->rgoAction[action].guidInstance = device->ddi.guidInstance;
364     lpdiaf->rgoAction[action].dwHow = DIAH_USERCONFIG;
365 
366     /* Set new action in the list */
367     lv_set_action(dialog, obj, action, lpdiaf);
368 }
369 
370 static void reset_actions(HWND dialog)
371 {
372     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
373     DIDevicesData *ddata = (DIDevicesData*) &data->devices_data;
374     unsigned i, j;
375 
376     for (i = 0; i < data->devices_data.ndevices; i++)
377     {
378         DeviceData *device = &ddata->devices[i];
379         for (j = 0; j < data->nusernames; j++)
380             IDirectInputDevice8_BuildActionMap(device->lpdid, &device->user_afs[j], data->usernames[j], DIDBAM_HWDEFAULTS);
381     }
382 }
383 
384 static void save_actions(HWND dialog) {
385     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
386     DIDevicesData *ddata = (DIDevicesData*) &data->devices_data;
387     unsigned i, j;
388     if (!data->display_only) {
389         for (i = 0; i < ddata->ndevices; i++)
390         {
391             DeviceData *device = &ddata->devices[i];
392             for (j = 0; j < data->nusernames; j++)
393             {
394                 if (save_mapping_settings(device->lpdid, &device->user_afs[j], data->usernames[j]) != DI_OK)
395                     MessageBoxA(dialog, "Could not save settings", 0, MB_ICONERROR);
396             }
397         }
398     }
399 }
400 
401 static INT_PTR CALLBACK ConfigureDevicesDlgProc(HWND dialog, UINT uMsg, WPARAM wParam, LPARAM lParam)
402 {
403     switch(uMsg)
404     {
405         case WM_INITDIALOG:
406         {
407             ConfigureDevicesData *data = (ConfigureDevicesData*) lParam;
408 
409             /* Initialize action format and enumerate devices */
410             init_devices(dialog, data);
411 
412             /* Store information in the window */
413             SetWindowLongPtrW(dialog, DWLP_USER, (LONG_PTR) data);
414 
415             init_listview_columns(dialog);
416 
417             /* Select the first device and show its actions */
418             SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_SETCURSEL, 0, 0);
419             SendDlgItemMessageW(dialog, IDC_PLAYERCOMBO, CB_SETCURSEL, 0, 0);
420             fill_device_object_list(dialog);
421 
422             ShowCursor(TRUE);
423 
424             break;
425         }
426 
427         case WM_DESTROY:
428             ShowCursor(FALSE);
429             break;
430 
431         case WM_NOTIFY:
432 
433             switch (((LPNMHDR)lParam)->code)
434             {
435                 case LVN_ITEMCHANGED:
436                     show_suitable_actions(dialog);
437                     break;
438             }
439             break;
440 
441 
442         case WM_COMMAND:
443 
444             switch(LOWORD(wParam))
445             {
446 
447                 case IDC_ACTIONLIST:
448 
449                     switch (HIWORD(wParam))
450                     {
451                         case LBN_DBLCLK:
452                             /* Ignore this if app did not ask for editing */
453                             if (dialog_display_only(dialog)) break;
454 
455                             assign_action(dialog);
456                             break;
457                     }
458                     break;
459 
460                 case IDC_CONTROLLERCOMBO:
461                 case IDC_PLAYERCOMBO:
462 
463                     switch (HIWORD(wParam))
464                     {
465                         case CBN_SELCHANGE:
466                             fill_device_object_list(dialog);
467                             break;
468                     }
469                     break;
470 
471                 case IDOK:
472                     save_actions(dialog);
473                     EndDialog(dialog, 0);
474                     destroy_data(dialog);
475                     break;
476 
477                 case IDCANCEL:
478                     EndDialog(dialog, 0);
479                     destroy_data(dialog);
480                     break;
481 
482                 case IDC_RESET:
483                     reset_actions(dialog);
484                     fill_device_object_list(dialog);
485                     break;
486             }
487         break;
488     }
489 
490     return FALSE;
491 }
492 
493 HRESULT _configure_devices(IDirectInput8W *iface,
494                            LPDICONFIGUREDEVICESCALLBACK lpdiCallback,
495                            LPDICONFIGUREDEVICESPARAMSW lpdiCDParams,
496                            DWORD dwFlags,
497                            LPVOID pvRefData
498 )
499 {
500     int i;
501     DWORD size;
502     WCHAR *username = NULL;
503     ConfigureDevicesData data;
504     data.lpDI = iface;
505     data.original_lpdiaf = lpdiCDParams->lprgFormats;
506     data.display_only = !(dwFlags & DICD_EDIT);
507     data.nusernames = lpdiCDParams->dwcUsers;
508     if (lpdiCDParams->lptszUserNames == NULL)
509     {
510         /* Get default user name */
511         GetUserNameW(NULL, &size);
512         username = heap_alloc(size * sizeof(WCHAR) );
513         GetUserNameW(username, &size);
514         data.nusernames = 1;
515         data.usernames = heap_alloc(sizeof(WCHAR *));
516         data.usernames[0] = username;
517     }
518     else
519     {
520         WCHAR *p = lpdiCDParams->lptszUserNames;
521         data.usernames = heap_alloc(sizeof(WCHAR *) * data.nusernames);
522         for (i = 0; i < data.nusernames; i++)
523         {
524             if (*p)
525             {
526                 data.usernames[i] = p;
527                 while (*(p++));
528             }
529             else
530                 /* Return if there is an empty string */
531                 return DIERR_INVALIDPARAM;
532         }
533     }
534 
535     InitCommonControls();
536 
537     DialogBoxParamW(DINPUT_instance, (const WCHAR *)MAKEINTRESOURCE(IDD_CONFIGUREDEVICES),
538             lpdiCDParams->hwnd, ConfigureDevicesDlgProc, (LPARAM)&data);
539 
540     heap_free(username);
541     heap_free(data.usernames);
542 
543     return DI_OK;
544 }
545