winegstreamer: Set 'max_threads' to 4 for 32-bit processors.
[wine.git] / programs / winecfg / audio.c
blobb98efbc2ec65aff009d903a930a856bb71a08150
1 /*
2 * Audio management UI code
4 * Copyright 2004 Chris Morgan
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.
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.
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
22 #define WIN32_LEAN_AND_MEAN
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #define COBJMACROS
29 #include <windows.h>
30 #include <wine/debug.h>
31 #include <shellapi.h>
32 #include <objbase.h>
33 #include <shlguid.h>
34 #include <shlwapi.h>
35 #include <shlobj.h>
36 #include <mmsystem.h>
37 #include <mmreg.h>
38 #include <mmddk.h>
40 #include "ole2.h"
41 #include "propkey.h"
42 #include "initguid.h"
43 #include "propkeydef.h"
44 #include "devpkey.h"
45 #include "mmdeviceapi.h"
46 #include "audioclient.h"
47 #include "audiopolicy.h"
49 #include "winecfg.h"
50 #include "resource.h"
52 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
54 struct DeviceInfo {
55 WCHAR *id;
56 PROPVARIANT name;
57 int speaker_config;
60 static WCHAR g_drv_keyW[256] = L"Software\\Wine\\Drivers\\";
62 static UINT num_render_devs, num_capture_devs;
63 static struct DeviceInfo *render_devs, *capture_devs;
65 static const struct
67 int text_id;
68 DWORD speaker_mask;
69 } speaker_configs[] =
71 { IDS_AUDIO_SPEAKER_5POINT1, KSAUDIO_SPEAKER_5POINT1 },
72 { IDS_AUDIO_SPEAKER_QUAD, KSAUDIO_SPEAKER_QUAD },
73 { IDS_AUDIO_SPEAKER_STEREO, KSAUDIO_SPEAKER_STEREO },
74 { IDS_AUDIO_SPEAKER_MONO, KSAUDIO_SPEAKER_MONO },
75 { 0, 0 }
78 static BOOL load_device(IMMDevice *dev, struct DeviceInfo *info)
80 IPropertyStore *ps;
81 HRESULT hr;
82 PROPVARIANT pv;
83 UINT i;
85 hr = IMMDevice_GetId(dev, &info->id);
86 if(FAILED(hr)){
87 info->id = NULL;
88 return FALSE;
91 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &ps);
92 if(FAILED(hr)){
93 CoTaskMemFree(info->id);
94 info->id = NULL;
95 return FALSE;
98 PropVariantInit(&info->name);
100 hr = IPropertyStore_GetValue(ps,
101 (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &info->name);
102 if(FAILED(hr)){
103 CoTaskMemFree(info->id);
104 info->id = NULL;
105 IPropertyStore_Release(ps);
106 return FALSE;
109 PropVariantInit(&pv);
111 hr = IPropertyStore_GetValue(ps,
112 &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);
114 info->speaker_config = -1;
115 if(SUCCEEDED(hr) && pv.vt == VT_UI4){
116 i = 0;
117 while (speaker_configs[i].text_id != 0) {
118 if ((speaker_configs[i].speaker_mask & pv.ulVal) == speaker_configs[i].speaker_mask) {
119 info->speaker_config = i;
120 break;
122 i++;
126 /* fallback to stereo */
127 if(info->speaker_config == -1)
128 info->speaker_config = 2;
130 IPropertyStore_Release(ps);
132 return TRUE;
135 static BOOL load_devices(IMMDeviceEnumerator *devenum, EDataFlow dataflow,
136 UINT *ndevs, struct DeviceInfo **out)
138 IMMDeviceCollection *coll;
139 UINT i;
140 HRESULT hr;
142 hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, dataflow,
143 DEVICE_STATE_ACTIVE, &coll);
144 if(FAILED(hr))
145 return FALSE;
147 hr = IMMDeviceCollection_GetCount(coll, ndevs);
148 if(FAILED(hr)){
149 IMMDeviceCollection_Release(coll);
150 return FALSE;
153 if(*ndevs > 0){
154 *out = malloc(sizeof(struct DeviceInfo) * (*ndevs));
155 if(!*out){
156 IMMDeviceCollection_Release(coll);
157 return FALSE;
160 for(i = 0; i < *ndevs; ++i){
161 IMMDevice *dev;
163 hr = IMMDeviceCollection_Item(coll, i, &dev);
164 if(FAILED(hr)){
165 (*out)[i].id = NULL;
166 continue;
169 load_device(dev, &(*out)[i]);
171 IMMDevice_Release(dev);
173 }else
174 *out = NULL;
176 IMMDeviceCollection_Release(coll);
178 return TRUE;
181 static BOOL get_driver_name(IMMDeviceEnumerator *devenum, PROPVARIANT *pv)
183 IMMDevice *device;
184 IPropertyStore *ps;
185 HRESULT hr;
187 hr = IMMDeviceEnumerator_GetDevice(devenum, L"Wine info device", &device);
188 if(FAILED(hr))
189 return FALSE;
191 hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
192 if(FAILED(hr)){
193 IMMDevice_Release(device);
194 return FALSE;
197 hr = IPropertyStore_GetValue(ps,
198 (const PROPERTYKEY *)&DEVPKEY_Device_Driver, pv);
199 IPropertyStore_Release(ps);
200 IMMDevice_Release(device);
201 if(FAILED(hr))
202 return FALSE;
204 return TRUE;
207 static void initAudioDlg (HWND hDlg)
209 WCHAR display_str[256], format_str[256], sysdefault_str[256], disabled_str[64];
210 IMMDeviceEnumerator *devenum;
211 BOOL have_driver = FALSE;
212 HRESULT hr;
213 UINT i;
214 LVCOLUMNW lvcol;
215 WCHAR colW[64], speaker_str[256];
216 RECT rect;
217 DWORD width;
219 WINE_TRACE("\n");
221 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER, format_str, ARRAY_SIZE(format_str));
222 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER_NONE, disabled_str,
223 ARRAY_SIZE(disabled_str));
224 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SYSDEFAULT, sysdefault_str,
225 ARRAY_SIZE(sysdefault_str));
227 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
228 CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
229 if(SUCCEEDED(hr)){
230 PROPVARIANT pv;
232 load_devices(devenum, eRender, &num_render_devs, &render_devs);
233 load_devices(devenum, eCapture, &num_capture_devs, &capture_devs);
235 PropVariantInit(&pv);
236 if(get_driver_name(devenum, &pv) && pv.pwszVal[0] != '\0'){
237 have_driver = TRUE;
238 swprintf(display_str, ARRAY_SIZE(display_str), format_str, pv.pwszVal);
239 lstrcatW(g_drv_keyW, pv.pwszVal);
241 PropVariantClear(&pv);
243 IMMDeviceEnumerator_Release(devenum);
246 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
247 0, (LPARAM)sysdefault_str);
248 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, 0, 0);
249 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
250 0, (LPARAM)sysdefault_str);
251 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, 0, 0);
253 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
254 0, (LPARAM)sysdefault_str);
255 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, 0, 0);
256 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
257 0, (LPARAM)sysdefault_str);
258 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, 0, 0);
260 i = 0;
261 while (speaker_configs[i].text_id != 0) {
262 LoadStringW(GetModuleHandleW(NULL), speaker_configs[i].text_id, speaker_str,
263 ARRAY_SIZE(speaker_str));
265 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_ADDSTRING,
266 0, (LPARAM)speaker_str);
268 i++;
271 GetClientRect(GetDlgItem(hDlg, IDC_LIST_AUDIO_DEVICES), &rect);
272 width = (rect.right - rect.left) * 3 / 5;
274 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DEVICE, colW, ARRAY_SIZE(colW));
275 lvcol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
276 lvcol.pszText = colW;
277 lvcol.cchTextMax = lstrlenW(colW);
278 lvcol.cx = width;
279 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvcol);
281 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SPEAKER_CONFIG, colW, ARRAY_SIZE(colW));
282 lvcol.pszText = colW;
283 lvcol.cchTextMax = lstrlenW(colW);
284 lvcol.cx = rect.right - rect.left - width;
285 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvcol);
287 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
289 if(have_driver){
290 WCHAR *reg_out_dev, *reg_vout_dev, *reg_in_dev, *reg_vin_dev;
292 reg_out_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultOutput", NULL);
293 reg_vout_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceOutput", NULL);
294 reg_in_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultInput", NULL);
295 reg_vin_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceInput", NULL);
297 for(i = 0; i < num_render_devs; ++i){
298 LVITEMW lvitem;
300 if(!render_devs[i].id)
301 continue;
303 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
304 0, (LPARAM)render_devs[i].name.pwszVal);
305 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETITEMDATA,
306 i + 1, (LPARAM)&render_devs[i]);
308 if(reg_out_dev && !wcscmp(render_devs[i].id, reg_out_dev)){
309 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
310 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL, render_devs[i].speaker_config, 0);
313 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
314 0, (LPARAM)render_devs[i].name.pwszVal);
315 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETITEMDATA,
316 i + 1, (LPARAM)&render_devs[i]);
317 if(reg_vout_dev && !wcscmp(render_devs[i].id, reg_vout_dev))
318 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
320 lvitem.mask = LVIF_TEXT | LVIF_PARAM;
321 lvitem.iItem = i;
322 lvitem.iSubItem = 0;
323 lvitem.pszText = render_devs[i].name.pwszVal;
324 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
325 lvitem.lParam = (LPARAM)&render_devs[i];
327 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTITEMW, 0, (LPARAM)&lvitem);
329 LoadStringW(GetModuleHandleW(NULL), speaker_configs[render_devs[i].speaker_config].text_id,
330 speaker_str, ARRAY_SIZE(speaker_str));
332 lvitem.mask = LVIF_TEXT;
333 lvitem.iItem = i;
334 lvitem.iSubItem = 1;
335 lvitem.pszText = speaker_str;
336 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
338 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
341 for(i = 0; i < num_capture_devs; ++i){
342 if(!capture_devs[i].id)
343 continue;
345 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
346 0, (LPARAM)capture_devs[i].name.pwszVal);
347 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETITEMDATA,
348 i + 1, (LPARAM)&capture_devs[i]);
349 if(reg_in_dev && !wcscmp(capture_devs[i].id, reg_in_dev))
350 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, i + 1, 0);
352 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
353 0, (LPARAM)capture_devs[i].name.pwszVal);
354 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETITEMDATA,
355 i + 1, (LPARAM)&capture_devs[i]);
356 if(reg_vin_dev && !wcscmp(capture_devs[i].id, reg_vin_dev))
357 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, i + 1, 0);
360 free(reg_out_dev);
361 free(reg_vout_dev);
362 free(reg_in_dev);
363 free(reg_vin_dev);
364 }else
365 swprintf(display_str, ARRAY_SIZE(display_str), format_str, disabled_str);
367 SetDlgItemTextW(hDlg, IDC_AUDIO_DRIVER, display_str);
370 static void set_reg_device(HWND hDlg, int dlgitem, const WCHAR *key_name)
372 UINT idx;
373 struct DeviceInfo *info;
375 idx = SendDlgItemMessageW(hDlg, dlgitem, CB_GETCURSEL, 0, 0);
377 info = (struct DeviceInfo *)SendDlgItemMessageW(hDlg, dlgitem,
378 CB_GETITEMDATA, idx, 0);
380 if(!info || info == (void*)CB_ERR)
381 set_reg_key(HKEY_CURRENT_USER, g_drv_keyW, key_name, NULL);
382 else
383 set_reg_key(HKEY_CURRENT_USER, g_drv_keyW, key_name, info->id);
386 static void test_sound(void)
388 if(!PlaySoundW(MAKEINTRESOURCEW(IDW_TESTSOUND), NULL, SND_RESOURCE | SND_ASYNC)){
389 WCHAR error_str[256], title_str[256];
391 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED, error_str,
392 ARRAY_SIZE(error_str));
393 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED_TITLE, title_str,
394 ARRAY_SIZE(title_str));
396 MessageBoxW(NULL, error_str, title_str, MB_OK | MB_ICONERROR);
400 static void apply_speaker_configs(void)
402 UINT i;
403 IMMDeviceEnumerator *devenum;
404 IMMDevice *dev;
405 IPropertyStore *ps;
406 PROPVARIANT pv;
407 HRESULT hr;
409 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
410 CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
412 if(FAILED(hr)){
413 ERR("Unable to create MMDeviceEnumerator: 0x%08lx\n", hr);
414 return;
417 PropVariantInit(&pv);
418 pv.vt = VT_UI4;
420 for (i = 0; i < num_render_devs; i++) {
421 hr = IMMDeviceEnumerator_GetDevice(devenum, render_devs[i].id, &dev);
423 if(FAILED(hr)){
424 WARN("Could not get MMDevice for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
425 continue;
428 hr = IMMDevice_OpenPropertyStore(dev, STGM_WRITE, &ps);
430 if(FAILED(hr)){
431 WARN("Could not open property store for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
432 IMMDevice_Release(dev);
433 continue;
436 pv.ulVal = speaker_configs[render_devs[i].speaker_config].speaker_mask;
438 hr = IPropertyStore_SetValue(ps, &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);
440 if (FAILED(hr))
441 WARN("IPropertyStore_SetValue failed for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
443 IPropertyStore_Release(ps);
444 IMMDevice_Release(dev);
447 IMMDeviceEnumerator_Release(devenum);
450 static void listview_changed(HWND hDlg)
452 int idx;
454 idx = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
455 if(idx < 0) {
456 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
457 return;
460 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL,
461 render_devs[idx].speaker_config, 0);
463 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 1);
466 INT_PTR CALLBACK
467 AudioDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
469 switch (uMsg) {
470 case WM_COMMAND:
471 switch (LOWORD(wParam)) {
472 case IDC_AUDIO_TEST:
473 test_sound();
474 break;
475 case IDC_AUDIOOUT_DEVICE:
476 if(HIWORD(wParam) == CBN_SELCHANGE){
477 set_reg_device(hDlg, IDC_AUDIOOUT_DEVICE, L"DefaultOutput");
478 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
480 break;
481 case IDC_VOICEOUT_DEVICE:
482 if(HIWORD(wParam) == CBN_SELCHANGE){
483 set_reg_device(hDlg, IDC_VOICEOUT_DEVICE, L"DefaultVoiceOutput");
484 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
486 break;
487 case IDC_AUDIOIN_DEVICE:
488 if(HIWORD(wParam) == CBN_SELCHANGE){
489 set_reg_device(hDlg, IDC_AUDIOIN_DEVICE, L"DefaultInput");
490 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
492 break;
493 case IDC_VOICEIN_DEVICE:
494 if(HIWORD(wParam) == CBN_SELCHANGE){
495 set_reg_device(hDlg, IDC_VOICEIN_DEVICE, L"DefaultVoiceInput");
496 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
498 break;
499 case IDC_SPEAKERCONFIG_SPEAKERS:
500 if(HIWORD(wParam) == CBN_SELCHANGE){
501 UINT dev, idx;
503 idx = SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_GETCURSEL, 0, 0);
504 dev = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
506 if(dev < num_render_devs){
507 WCHAR speaker_str[256];
508 LVITEMW lvitem;
510 render_devs[dev].speaker_config = idx;
512 LoadStringW(GetModuleHandleW(NULL), speaker_configs[idx].text_id,
513 speaker_str, ARRAY_SIZE(speaker_str));
515 lvitem.mask = LVIF_TEXT;
516 lvitem.iItem = dev;
517 lvitem.iSubItem = 1;
518 lvitem.pszText = speaker_str;
519 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
521 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
523 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
526 break;
528 break;
530 case WM_SHOWWINDOW:
531 set_window_title(hDlg);
532 break;
534 case WM_NOTIFY:
535 switch(((LPNMHDR)lParam)->code) {
536 case PSN_KILLACTIVE:
537 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, FALSE);
538 break;
539 case PSN_APPLY:
540 apply_speaker_configs();
541 apply();
542 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
543 break;
544 case PSN_SETACTIVE:
545 break;
546 case LVN_ITEMCHANGED:
547 listview_changed(hDlg);
548 break;
550 break;
551 case WM_INITDIALOG:
552 initAudioDlg(hDlg);
553 break;
556 return FALSE;