winecfg: Don't reload winmm for each audio test.
[wine] / programs / winecfg / audio.c
1 /*
2  * Audio management UI code
3  *
4  * Copyright 2004 Chris Morgan
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 WIN32_LEAN_AND_MEAN
23 #define NONAMELESSSTRUCT
24 #define NONAMELESSUNION
25
26 #include "config.h"
27 #include "wine/port.h"
28
29 #include <assert.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33
34 #define COBJMACROS
35 #include <windows.h>
36 #include <wine/debug.h>
37 #include <shellapi.h>
38 #include <objbase.h>
39 #include <shlguid.h>
40 #include <shlwapi.h>
41 #include <shlobj.h>
42 #include <mmsystem.h>
43 #include <mmreg.h>
44 #include <mmddk.h>
45
46 #include "ole2.h"
47 #include "initguid.h"
48 #include "devpkey.h"
49 #include "mmdeviceapi.h"
50 #include "audioclient.h"
51 #include "audiopolicy.h"
52
53 #include "winecfg.h"
54 #include "resource.h"
55
56 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
57
58 struct DeviceInfo {
59     WCHAR *id;
60     PROPVARIANT name;
61 };
62
63 static WCHAR g_drv_keyW[256] = {'S','o','f','t','w','a','r','e','\\',
64     'W','i','n','e','\\','D','r','i','v','e','r','s','\\',0};
65
66 static const WCHAR reg_out_nameW[] = {'D','e','f','a','u','l','t','O','u','t','p','u','t',0};
67 static const WCHAR reg_in_nameW[] = {'D','e','f','a','u','l','t','I','n','p','u','t',0};
68 static const WCHAR reg_vout_nameW[] = {'D','e','f','a','u','l','t','V','o','i','c','e','O','u','t','p','u','t',0};
69 static const WCHAR reg_vin_nameW[] = {'D','e','f','a','u','l','t','V','o','i','c','e','I','n','p','u','t',0};
70
71 static UINT num_render_devs, num_capture_devs;
72 static struct DeviceInfo *render_devs, *capture_devs;
73
74 static BOOL load_device(IMMDevice *dev, struct DeviceInfo *info)
75 {
76     IPropertyStore *ps;
77     HRESULT hr;
78
79     hr = IMMDevice_GetId(dev, &info->id);
80     if(FAILED(hr)){
81         info->id = NULL;
82         return FALSE;
83     }
84
85     hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &ps);
86     if(FAILED(hr)){
87         CoTaskMemFree(info->id);
88         info->id = NULL;
89         return FALSE;
90     }
91
92     PropVariantInit(&info->name);
93
94     hr = IPropertyStore_GetValue(ps,
95             (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &info->name);
96     IPropertyStore_Release(ps);
97     if(FAILED(hr)){
98         CoTaskMemFree(info->id);
99         info->id = NULL;
100         return FALSE;
101     }
102
103     return TRUE;
104 }
105
106 static BOOL load_devices(IMMDeviceEnumerator *devenum, EDataFlow dataflow,
107         UINT *ndevs, struct DeviceInfo **out)
108 {
109     IMMDeviceCollection *coll;
110     UINT i;
111     HRESULT hr;
112
113     hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, dataflow,
114             DEVICE_STATE_ACTIVE, &coll);
115     if(FAILED(hr))
116         return FALSE;
117
118     hr = IMMDeviceCollection_GetCount(coll, ndevs);
119     if(FAILED(hr)){
120         IMMDeviceCollection_Release(coll);
121         return FALSE;
122     }
123
124     if(*ndevs > 0){
125         *out = HeapAlloc(GetProcessHeap(), 0,
126                 sizeof(struct DeviceInfo) * (*ndevs));
127         if(!*out){
128             IMMDeviceCollection_Release(coll);
129             return FALSE;
130         }
131
132         for(i = 0; i < *ndevs; ++i){
133             IMMDevice *dev;
134
135             hr = IMMDeviceCollection_Item(coll, i, &dev);
136             if(FAILED(hr)){
137                 (*out)[i].id = NULL;
138                 continue;
139             }
140
141             load_device(dev, &(*out)[i]);
142
143             IMMDevice_Release(dev);
144         }
145     }else
146         *out = NULL;
147
148     IMMDeviceCollection_Release(coll);
149
150     return TRUE;
151 }
152
153 static BOOL get_driver_name(IMMDeviceEnumerator *devenum, PROPVARIANT *pv)
154 {
155     IMMDevice *device;
156     IPropertyStore *ps;
157     HRESULT hr;
158
159     static const WCHAR wine_info_deviceW[] = {'W','i','n','e',' ',
160         'i','n','f','o',' ','d','e','v','i','c','e',0};
161
162     hr = IMMDeviceEnumerator_GetDevice(devenum, wine_info_deviceW, &device);
163     if(FAILED(hr))
164         return FALSE;
165
166     hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
167     if(FAILED(hr)){
168         IMMDevice_Release(device);
169         return FALSE;
170     }
171
172     hr = IPropertyStore_GetValue(ps,
173             (const PROPERTYKEY *)&DEVPKEY_Device_Driver, pv);
174     IPropertyStore_Release(ps);
175     IMMDevice_Release(device);
176     if(FAILED(hr))
177         return FALSE;
178
179     return TRUE;
180 }
181
182 static void initAudioDlg (HWND hDlg)
183 {
184     WCHAR display_str[256], format_str[256], sysdefault_str[256], disabled_str[64];
185     IMMDeviceEnumerator *devenum;
186     BOOL have_driver = FALSE;
187     HRESULT hr;
188
189     WINE_TRACE("\n");
190
191     LoadStringW(GetModuleHandle(NULL), IDS_AUDIO_DRIVER,
192             format_str, sizeof(format_str) / sizeof(*format_str));
193     LoadStringW(GetModuleHandle(NULL), IDS_AUDIO_DRIVER_NONE,
194             disabled_str, sizeof(disabled_str) / sizeof(*disabled_str));
195     LoadStringW(GetModuleHandle(NULL), IDS_AUDIO_SYSDEFAULT,
196             sysdefault_str, sizeof(sysdefault_str) / sizeof(*sysdefault_str));
197
198     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
199             CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
200     if(SUCCEEDED(hr)){
201         PROPVARIANT pv;
202
203         load_devices(devenum, eRender, &num_render_devs, &render_devs);
204         load_devices(devenum, eCapture, &num_capture_devs, &capture_devs);
205
206         PropVariantInit(&pv);
207         if(get_driver_name(devenum, &pv) && pv.u.pwszVal[0] != '\0'){
208             have_driver = TRUE;
209             wnsprintfW(display_str, sizeof(display_str) / sizeof(*display_str),
210                     format_str, pv.u.pwszVal);
211             lstrcatW(g_drv_keyW, pv.u.pwszVal);
212         }
213         PropVariantClear(&pv);
214
215         IMMDeviceEnumerator_Release(devenum);
216     }
217
218     SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
219             0, (LPARAM)sysdefault_str);
220     SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, 0, 0);
221     SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
222             0, (LPARAM)sysdefault_str);
223     SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, 0, 0);
224
225     SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
226             0, (LPARAM)sysdefault_str);
227     SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, 0, 0);
228     SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
229             0, (LPARAM)sysdefault_str);
230     SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, 0, 0);
231
232     if(have_driver){
233         WCHAR *reg_out_dev, *reg_vout_dev, *reg_in_dev, *reg_vin_dev;
234         UINT i;
235
236         reg_out_dev = get_reg_keyW(HKEY_CURRENT_USER, g_drv_keyW, reg_out_nameW, NULL);
237         reg_vout_dev = get_reg_keyW(HKEY_CURRENT_USER, g_drv_keyW, reg_vout_nameW, NULL);
238         reg_in_dev = get_reg_keyW(HKEY_CURRENT_USER, g_drv_keyW, reg_in_nameW, NULL);
239         reg_vin_dev = get_reg_keyW(HKEY_CURRENT_USER, g_drv_keyW, reg_vin_nameW, NULL);
240
241         for(i = 0; i < num_render_devs; ++i){
242             if(!render_devs[i].id)
243                 continue;
244
245             SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
246                     0, (LPARAM)render_devs[i].name.u.pwszVal);
247             SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETITEMDATA,
248                     i + 1, (LPARAM)&render_devs[i]);
249             if(reg_out_dev && !lstrcmpW(render_devs[i].id, reg_out_dev))
250                 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
251
252             SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
253                     0, (LPARAM)render_devs[i].name.u.pwszVal);
254             SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETITEMDATA,
255                     i + 1, (LPARAM)&render_devs[i]);
256             if(reg_vout_dev && !lstrcmpW(render_devs[i].id, reg_vout_dev))
257                 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
258         }
259
260         for(i = 0; i < num_capture_devs; ++i){
261             if(!capture_devs[i].id)
262                 continue;
263
264             SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
265                     0, (LPARAM)capture_devs[i].name.u.pwszVal);
266             SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETITEMDATA,
267                     i + 1, (LPARAM)&capture_devs[i]);
268             if(reg_in_dev && !lstrcmpW(capture_devs[i].id, reg_in_dev))
269                 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, i + 1, 0);
270
271             SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
272                     0, (LPARAM)capture_devs[i].name.u.pwszVal);
273             SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETITEMDATA,
274                     i + 1, (LPARAM)&capture_devs[i]);
275             if(reg_vin_dev && !lstrcmpW(capture_devs[i].id, reg_vin_dev))
276                 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, i + 1, 0);
277         }
278
279         HeapFree(GetProcessHeap(), 0, reg_out_dev);
280         HeapFree(GetProcessHeap(), 0, reg_vout_dev);
281         HeapFree(GetProcessHeap(), 0, reg_in_dev);
282         HeapFree(GetProcessHeap(), 0, reg_vin_dev);
283     }else
284         wnsprintfW(display_str, sizeof(display_str) / sizeof(*display_str),
285                 format_str, disabled_str);
286
287     SetDlgItemTextW(hDlg, IDC_AUDIO_DRIVER, display_str);
288 }
289
290 static void set_reg_device(HWND hDlg, int dlgitem, const WCHAR *key_name)
291 {
292     UINT idx;
293     struct DeviceInfo *info;
294
295     idx = SendDlgItemMessageW(hDlg, dlgitem, CB_GETCURSEL, 0, 0);
296
297     info = (struct DeviceInfo *)SendDlgItemMessageW(hDlg, dlgitem,
298             CB_GETITEMDATA, idx, 0);
299
300     if(!info || info == (void*)CB_ERR)
301         set_reg_keyW(HKEY_CURRENT_USER, g_drv_keyW, key_name, NULL);
302     else
303         set_reg_keyW(HKEY_CURRENT_USER, g_drv_keyW, key_name, info->id);
304 }
305
306 static void test_sound(void)
307 {
308     if(!PlaySoundW(MAKEINTRESOURCEW(IDW_TESTSOUND), NULL, SND_RESOURCE | SND_SYNC)){
309         WCHAR error_str[256], title_str[256];
310
311         LoadStringW(GetModuleHandle(NULL), IDS_AUDIO_TEST_FAILED,
312                 error_str, sizeof(error_str) / sizeof(*error_str));
313         LoadStringW(GetModuleHandle(NULL), IDS_AUDIO_TEST_FAILED_TITLE,
314                 title_str, sizeof(title_str) / sizeof(*title_str));
315
316         MessageBoxW(NULL, error_str, title_str, MB_OK | MB_ICONERROR);
317     }
318 }
319
320 INT_PTR CALLBACK
321 AudioDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
322 {
323   switch (uMsg) {
324       case WM_COMMAND:
325         switch (LOWORD(wParam)) {
326           case IDC_AUDIO_TEST:
327               test_sound();
328               break;
329           case IDC_AUDIOOUT_DEVICE:
330               if(HIWORD(wParam) == CBN_SELCHANGE){
331                   set_reg_device(hDlg, IDC_AUDIOOUT_DEVICE, reg_out_nameW);
332                   SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
333               }
334               break;
335           case IDC_VOICEOUT_DEVICE:
336               if(HIWORD(wParam) == CBN_SELCHANGE){
337                   set_reg_device(hDlg, IDC_VOICEOUT_DEVICE, reg_vout_nameW);
338                   SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
339               }
340               break;
341           case IDC_AUDIOIN_DEVICE:
342               if(HIWORD(wParam) == CBN_SELCHANGE){
343                   set_reg_device(hDlg, IDC_AUDIOIN_DEVICE, reg_in_nameW);
344                   SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
345               }
346               break;
347           case IDC_VOICEIN_DEVICE:
348               if(HIWORD(wParam) == CBN_SELCHANGE){
349                   set_reg_device(hDlg, IDC_VOICEIN_DEVICE, reg_vin_nameW);
350                   SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
351               }
352               break;
353         }
354         break;
355
356       case WM_SHOWWINDOW:
357         set_window_title(hDlg);
358         break;
359
360       case WM_NOTIFY:
361         switch(((LPNMHDR)lParam)->code) {
362             case PSN_KILLACTIVE:
363               SetWindowLongPtr(hDlg, DWLP_MSGRESULT, FALSE);
364               break;
365             case PSN_APPLY:
366               apply();
367               SetWindowLongPtr(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
368               break;
369             case PSN_SETACTIVE:
370               break;
371         }
372         break;
373       case WM_INITDIALOG:
374         initAudioDlg(hDlg);
375         break;
376   }
377
378   return FALSE;
379 }