dinput: Fix compilation on systems that don't support nameless unions.
[wine] / dlls / dinput / config.c
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 #include "wine/debug.h"
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 WINE_DEFAULT_DEBUG_CHANNEL(dinput);
29
30 typedef struct {
31     int nobjects;
32     IDirectInputDevice8W *lpdid;
33     DIDEVICEINSTANCEW ddi;
34     DIDEVICEOBJECTINSTANCEW ddo[256];
35 } DeviceData;
36
37 typedef struct {
38     int ndevices;
39     DeviceData *devices;
40 } DIDevicesData;
41
42 typedef struct {
43     IDirectInput8W *lpDI;
44     LPDIACTIONFORMATW lpdiaf;
45     LPDIACTIONFORMATW original_lpdiaf;
46     DIDevicesData devices_data;
47     int display_only;
48 } ConfigureDevicesData;
49
50 /*
51  * Enumeration callback functions
52  */
53 static BOOL CALLBACK collect_objects(LPCDIDEVICEOBJECTINSTANCEW lpddo, LPVOID pvRef)
54 {
55     DeviceData *data = (DeviceData*) pvRef;
56
57     data->ddo[data->nobjects] = *lpddo;
58
59     data->nobjects++;
60     return DIENUM_CONTINUE;
61 }
62
63 static BOOL CALLBACK count_devices(LPCDIDEVICEINSTANCEW lpddi, IDirectInputDevice8W *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef)
64 {
65     DIDevicesData *data = (DIDevicesData*) pvRef;
66
67     data->ndevices++;
68     return DIENUM_CONTINUE;
69 }
70
71 static BOOL CALLBACK collect_devices(LPCDIDEVICEINSTANCEW lpddi, IDirectInputDevice8W *lpdid, DWORD dwFlags, DWORD dwRemaining, LPVOID pvRef)
72 {
73     DIDevicesData *data = (DIDevicesData*) pvRef;
74     DeviceData *device = &data->devices[data->ndevices];
75     device->lpdid = lpdid;
76     device->ddi = *lpddi;
77
78     IDirectInputDevice_AddRef(lpdid);
79
80     device->nobjects = 0;
81     IDirectInputDevice_EnumObjects(lpdid, collect_objects, (LPVOID) device, DIDFT_ALL);
82
83     data->ndevices++;
84     return DIENUM_CONTINUE;
85 }
86
87 /*
88  * Listview utility functions
89  */
90 static void init_listview_columns(HWND dialog)
91 {
92     HINSTANCE hinstance = (HINSTANCE) GetWindowLongPtrW(dialog, GWLP_HINSTANCE);
93     LVCOLUMNW listColumn;
94     RECT viewRect;
95     int width;
96     WCHAR column[MAX_PATH];
97
98     GetClientRect(GetDlgItem(dialog, IDC_DEVICEOBJECTSLIST), &viewRect);
99     width = (viewRect.right - viewRect.left)/2;
100
101     LoadStringW(hinstance, IDS_OBJECTCOLUMN, column, sizeof(column)/sizeof(column[0]));
102     listColumn.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
103     listColumn.pszText = column;
104     listColumn.cchTextMax = lstrlenW(listColumn.pszText);
105     listColumn.cx = width;
106
107     SendDlgItemMessageW (dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTCOLUMNW, 0, (LPARAM) &listColumn);
108
109     LoadStringW(hinstance, IDS_ACTIONCOLUMN, column, sizeof(column)/sizeof(column[0]));
110     listColumn.cx = width;
111     listColumn.pszText = column;
112     listColumn.cchTextMax = lstrlenW(listColumn.pszText);
113
114     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTCOLUMNW, 1, (LPARAM) &listColumn);
115 }
116
117 static int lv_get_cur_item(HWND dialog)
118 {
119     return SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
120 }
121
122 static int lv_get_item_data(HWND dialog, int index)
123 {
124     LVITEMW item;
125
126     if (index < 0) return -1;
127
128     item.mask = LVIF_PARAM;
129     item.iItem = index;
130     item.iSubItem = 0;
131
132     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_GETITEMW , 0, (LPARAM)&item);
133
134     return item.lParam;
135 }
136
137 static void lv_set_action(HWND dialog, int item, int action, LPDIACTIONFORMATW lpdiaf)
138 {
139     static const WCHAR no_action[] = {'-','\0'};
140     const WCHAR *action_text = no_action;
141     LVITEMW lvItem;
142
143     if (item < 0) return;
144
145     if (action != -1)
146         action_text = lpdiaf->rgoAction[action].u.lptszActionName;
147
148     /* Keep the action and text in the listview item */
149     lvItem.iItem = item;
150
151     lvItem.mask = LVIF_PARAM;
152     lvItem.iSubItem = 0;
153     lvItem.lParam = (LPARAM) action;
154
155     /* Action index */
156     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_SETITEMW, 0, (LPARAM) &lvItem);
157
158     lvItem.mask = LVIF_TEXT;
159     lvItem.iSubItem = 1;
160     lvItem.pszText = (WCHAR *)action_text;
161     lvItem.cchTextMax = lstrlenW(lvItem.pszText);
162
163     /* Text */
164     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_SETITEMW, 0, (LPARAM) &lvItem);
165 }
166
167 /*
168  * Utility functions
169  */
170 static DeviceData* get_cur_device(HWND dialog)
171 {
172     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
173     int sel = SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_GETCURSEL, 0, 0);
174     return &data->devices_data.devices[sel];
175 }
176
177 static LPDIACTIONFORMATW get_cur_lpdiaf(HWND dialog)
178 {
179     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
180     return data->lpdiaf;
181 }
182
183 static int dialog_display_only(HWND dialog)
184 {
185     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
186     return data->display_only;
187 }
188
189 static void init_devices(HWND dialog, IDirectInput8W *lpDI, DIDevicesData *data, LPDIACTIONFORMATW lpdiaf)
190 {
191     int i;
192
193     /* Count devices */
194     IDirectInput8_EnumDevicesBySemantics(lpDI, NULL, lpdiaf, count_devices, (LPVOID) data, 0);
195
196     /* Allocate devices */
197     data->devices = (DeviceData*) HeapAlloc(GetProcessHeap(), 0, sizeof(DeviceData) * data->ndevices);
198
199     /* Collect and insert */
200     data->ndevices = 0;
201     IDirectInput8_EnumDevicesBySemantics(lpDI, NULL, lpdiaf, collect_devices, (LPVOID) data, 0);
202
203     for (i=0; i < data->ndevices; i++)
204         SendDlgItemMessageW(dialog, IDC_CONTROLLERCOMBO, CB_ADDSTRING, 0, (LPARAM) data->devices[i].ddi.tszProductName );
205 }
206
207 static void destroy_data(HWND dialog)
208 {
209     int i;
210     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
211     DIDevicesData *devices_data = &data->devices_data;
212
213     /* Free the devices */
214     for (i=0; i < devices_data->ndevices; i++)
215         IDirectInputDevice8_Release(devices_data->devices[i].lpdid);
216
217     HeapFree(GetProcessHeap(), 0, devices_data->devices);
218
219     /* Free the backup LPDIACTIONFORMATW  */
220     HeapFree(GetProcessHeap(), 0, data->original_lpdiaf->rgoAction);
221     HeapFree(GetProcessHeap(), 0, data->original_lpdiaf);
222 }
223
224 static void fill_device_object_list(HWND dialog)
225 {
226     DeviceData *device = get_cur_device(dialog);
227     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
228     LVITEMW item;
229     int i, j;
230
231     /* Clean the listview */
232     SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_DELETEALLITEMS, 0, 0);
233
234     /* Add each object */
235     for (i=0; i < device->nobjects; i++)
236     {
237         int action = -1;
238
239         item.mask = LVIF_TEXT | LVIF_PARAM;
240         item.iItem = i;
241         item.iSubItem = 0;
242         item.pszText = device->ddo[i].tszName;
243         item.cchTextMax = lstrlenW(item.pszText);
244
245         /* Add the item */
246         SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_INSERTITEMW, 0, (LPARAM) &item);
247
248         /* Search for an assigned action  for this device */
249         for (j=0; j < lpdiaf->dwNumActions; j++)
250         {
251             if (IsEqualGUID(&lpdiaf->rgoAction[j].guidInstance, &device->ddi.guidInstance) &&
252                 lpdiaf->rgoAction[j].dwObjID == device->ddo[i].dwType)
253             {
254                 action = j;
255                 break;
256             }
257         }
258
259         lv_set_action(dialog, i, action, lpdiaf);
260     }
261 }
262
263 static void show_suitable_actions(HWND dialog)
264 {
265     DeviceData *device = get_cur_device(dialog);
266     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
267     int i, added = 0;
268     int obj = lv_get_cur_item(dialog);
269
270     if (obj < 0) return;
271
272     SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_RESETCONTENT, 0, 0);
273
274     for (i=0; i < lpdiaf->dwNumActions; i++)
275     {
276         /* Skip keyboard actions for non keyboards */
277         if (GET_DIDEVICE_TYPE(device->ddi.dwDevType) != DI8DEVTYPE_KEYBOARD &&
278             (lpdiaf->rgoAction[i].dwSemantic & DIKEYBOARD_MASK) == DIKEYBOARD_MASK) continue;
279
280         /* Skip mouse actions for non mouses */
281         if (GET_DIDEVICE_TYPE(device->ddi.dwDevType) != DI8DEVTYPE_MOUSE &&
282             (lpdiaf->rgoAction[i].dwSemantic & DIMOUSE_MASK) == DIMOUSE_MASK) continue;
283
284         /* Add action string and index in the action format to the list entry */
285         if (DIDFT_GETINSTANCE(lpdiaf->rgoAction[i].dwSemantic) & DIDFT_GETTYPE(device->ddo[obj].dwType))
286         {
287             SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_ADDSTRING, 0, (LPARAM)lpdiaf->rgoAction[i].u.lptszActionName);
288             SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_SETITEMDATA, added, (LPARAM) i);
289             added++;
290         }
291     }
292 }
293
294 static void assign_action(HWND dialog)
295 {
296     DeviceData *device = get_cur_device(dialog);
297     LPDIACTIONFORMATW lpdiaf = get_cur_lpdiaf(dialog);
298     LVFINDINFOW lvFind;
299     int sel = SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_GETCURSEL, 0, 0);
300     int action = SendDlgItemMessageW(dialog, IDC_ACTIONLIST, LB_GETITEMDATA, sel, 0);
301     int obj = lv_get_cur_item(dialog);
302     int old_action = lv_get_item_data(dialog, obj);
303     int used_obj;
304
305     DIDEVICEOBJECTINSTANCEW ddo = device->ddo[obj];
306
307     if (old_action == action) return;
308
309     /* Clear old action */
310     if (old_action != -1)
311     {
312         lpdiaf->rgoAction[old_action].dwObjID = 0;
313         lpdiaf->rgoAction[old_action].guidInstance = GUID_NULL;
314         lpdiaf->rgoAction[old_action].dwHow = DIAH_UNMAPPED;
315     }
316
317     /* Find if action text is already set for other object and unset it */
318     lvFind.flags = LVFI_PARAM;
319     lvFind.lParam = action;
320
321     used_obj = SendDlgItemMessageW(dialog, IDC_DEVICEOBJECTSLIST, LVM_FINDITEMW, -1, (LPARAM) &lvFind);
322
323     lv_set_action(dialog, used_obj, -1, lpdiaf);
324
325     /* Set new action */
326     lpdiaf->rgoAction[action].dwObjID = ddo.dwType;
327     lpdiaf->rgoAction[action].guidInstance = device->ddi.guidInstance;
328     lpdiaf->rgoAction[action].dwHow = DIAH_USERCONFIG;
329
330     /* Set new action in the list */
331     lv_set_action(dialog, obj, action, lpdiaf);
332 }
333
334 static void copy_actions(LPDIACTIONFORMATW to, LPDIACTIONFORMATW from)
335 {
336     int i;
337     for (i=0; i < from->dwNumActions; i++)
338     {
339         to->rgoAction[i].guidInstance = from->rgoAction[i].guidInstance;
340         to->rgoAction[i].dwObjID = from->rgoAction[i].dwObjID;
341         to->rgoAction[i].dwHow = from->rgoAction[i].dwHow;
342         to->rgoAction[i].u.lptszActionName = from->rgoAction[i].u.lptszActionName;
343     }
344 }
345
346 static void reset_actions(HWND dialog)
347 {
348     ConfigureDevicesData *data = (ConfigureDevicesData*) GetWindowLongPtrW(dialog, DWLP_USER);
349     LPDIACTIONFORMATW to = data->lpdiaf, from = data->original_lpdiaf;
350
351     copy_actions(to, from);
352 }
353
354 static INT_PTR CALLBACK ConfigureDevicesDlgProc(HWND dialog, UINT uMsg, WPARAM wParam, LPARAM lParam)
355 {
356     switch(uMsg)
357     {
358         case WM_INITDIALOG:
359         {
360             ConfigureDevicesData *data = (ConfigureDevicesData*) lParam;
361
362             /* Initialize action format and enumerate devices */
363             init_devices(dialog, data->lpDI, &data->devices_data, data->lpdiaf);
364
365             /* Store information in the window */
366             SetWindowLongPtrW(dialog, DWLP_USER, (LONG_PTR) data);
367
368             init_listview_columns(dialog);
369
370             /* Create a backup action format for CANCEL and RESET operations */
371             data->original_lpdiaf = HeapAlloc(GetProcessHeap(), 0, sizeof(LPDIACTIONFORMATW));
372             data->original_lpdiaf->dwNumActions = data->lpdiaf->dwNumActions;
373             data->original_lpdiaf->rgoAction = HeapAlloc(GetProcessHeap(), 0, sizeof(DIACTIONW)*data->lpdiaf->dwNumActions);
374             copy_actions(data->original_lpdiaf, data->lpdiaf);
375
376             break;
377         }
378
379         case WM_NOTIFY:
380
381             switch (((LPNMHDR)lParam)->code)
382             {
383                 case LVN_ITEMCHANGED:
384                     show_suitable_actions(dialog);
385                     break;
386             }
387             break;
388
389
390         case WM_COMMAND:
391
392             switch(LOWORD(wParam))
393             {
394
395                 case IDC_ACTIONLIST:
396
397                     switch (HIWORD(wParam))
398                     {
399                         case LBN_DBLCLK:
400                             /* Ignore this if app did not ask for editing */
401                             if (dialog_display_only(dialog)) break;
402
403                             assign_action(dialog);
404                             break;
405                     }
406                     break;
407
408                 case IDC_CONTROLLERCOMBO:
409
410                     switch (HIWORD(wParam))
411                     {
412                         case CBN_SELCHANGE:
413                             fill_device_object_list(dialog);
414                             break;
415                     }
416                     break;
417
418                 case IDOK:
419                     EndDialog(dialog, 0);
420                     destroy_data(dialog);
421                     break;
422
423                 case IDCANCEL:
424                     reset_actions(dialog);
425                     EndDialog(dialog, 0);
426                     destroy_data(dialog);
427                     break;
428
429                 case IDC_RESET:
430                     reset_actions(dialog);
431                     fill_device_object_list(dialog);
432                     break;
433             }
434         break;
435     }
436
437     return FALSE;
438 }
439
440 HRESULT _configure_devices(IDirectInput8W *iface,
441                            LPDICONFIGUREDEVICESCALLBACK lpdiCallback,
442                            LPDICONFIGUREDEVICESPARAMSW lpdiCDParams,
443                            DWORD dwFlags,
444                            LPVOID pvRefData
445 )
446 {
447     ConfigureDevicesData data;
448     data.lpDI = iface;
449     data.lpdiaf = lpdiCDParams->lprgFormats;
450     data.display_only = !(dwFlags & DICD_EDIT);
451
452     InitCommonControls();
453
454     DialogBoxParamW(GetModuleHandleA("dinput.dll"), (LPCWSTR) MAKEINTRESOURCE(IDD_CONFIGUREDEVICES), lpdiCDParams->hwnd, ConfigureDevicesDlgProc, (LPARAM) &data);
455
456     return DI_OK;
457 }