include: Fix a typo in _InterlockedXor64() intrinsic declaration.
[wine.git] / programs / winecfg / audio.c
blobcd73703a0dd05a52af5c2098c489c9ed2f153389
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
23 #define NONAMELESSUNION
25 #include <assert.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
30 #define COBJMACROS
31 #include <windows.h>
32 #include <wine/debug.h>
33 #include <shellapi.h>
34 #include <objbase.h>
35 #include <shlguid.h>
36 #include <shlwapi.h>
37 #include <shlobj.h>
38 #include <mmsystem.h>
39 #include <mmreg.h>
40 #include <mmddk.h>
42 #include "ole2.h"
43 #include "initguid.h"
44 #include "propkey.h"
45 #include "devpkey.h"
46 #include "mmdeviceapi.h"
47 #include "audioclient.h"
48 #include "audiopolicy.h"
50 #include "winecfg.h"
51 #include "resource.h"
53 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
55 struct DeviceInfo {
56 WCHAR *id;
57 PROPVARIANT name;
58 int speaker_config;
61 static WCHAR g_drv_keyW[256] = L"Software\\Wine\\Drivers\\";
63 static UINT num_render_devs, num_capture_devs;
64 static struct DeviceInfo *render_devs, *capture_devs;
66 static const struct
68 int text_id;
69 DWORD speaker_mask;
70 } speaker_configs[] =
72 { IDS_AUDIO_SPEAKER_5POINT1, KSAUDIO_SPEAKER_5POINT1 },
73 { IDS_AUDIO_SPEAKER_QUAD, KSAUDIO_SPEAKER_QUAD },
74 { IDS_AUDIO_SPEAKER_STEREO, KSAUDIO_SPEAKER_STEREO },
75 { IDS_AUDIO_SPEAKER_MONO, KSAUDIO_SPEAKER_MONO },
76 { 0, 0 }
79 static BOOL load_device(IMMDevice *dev, struct DeviceInfo *info)
81 IPropertyStore *ps;
82 HRESULT hr;
83 PROPVARIANT pv;
84 UINT i;
86 hr = IMMDevice_GetId(dev, &info->id);
87 if(FAILED(hr)){
88 info->id = NULL;
89 return FALSE;
92 hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &ps);
93 if(FAILED(hr)){
94 CoTaskMemFree(info->id);
95 info->id = NULL;
96 return FALSE;
99 PropVariantInit(&info->name);
101 hr = IPropertyStore_GetValue(ps,
102 (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &info->name);
103 if(FAILED(hr)){
104 CoTaskMemFree(info->id);
105 info->id = NULL;
106 IPropertyStore_Release(ps);
107 return FALSE;
110 PropVariantInit(&pv);
112 hr = IPropertyStore_GetValue(ps,
113 &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);
115 info->speaker_config = -1;
116 if(SUCCEEDED(hr) && pv.vt == VT_UI4){
117 i = 0;
118 while (speaker_configs[i].text_id != 0) {
119 if ((speaker_configs[i].speaker_mask & pv.ulVal) == speaker_configs[i].speaker_mask) {
120 info->speaker_config = i;
121 break;
123 i++;
127 /* fallback to stereo */
128 if(info->speaker_config == -1)
129 info->speaker_config = 2;
131 IPropertyStore_Release(ps);
133 return TRUE;
136 static BOOL load_devices(IMMDeviceEnumerator *devenum, EDataFlow dataflow,
137 UINT *ndevs, struct DeviceInfo **out)
139 IMMDeviceCollection *coll;
140 UINT i;
141 HRESULT hr;
143 hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, dataflow,
144 DEVICE_STATE_ACTIVE, &coll);
145 if(FAILED(hr))
146 return FALSE;
148 hr = IMMDeviceCollection_GetCount(coll, ndevs);
149 if(FAILED(hr)){
150 IMMDeviceCollection_Release(coll);
151 return FALSE;
154 if(*ndevs > 0){
155 *out = HeapAlloc(GetProcessHeap(), 0,
156 sizeof(struct DeviceInfo) * (*ndevs));
157 if(!*out){
158 IMMDeviceCollection_Release(coll);
159 return FALSE;
162 for(i = 0; i < *ndevs; ++i){
163 IMMDevice *dev;
165 hr = IMMDeviceCollection_Item(coll, i, &dev);
166 if(FAILED(hr)){
167 (*out)[i].id = NULL;
168 continue;
171 load_device(dev, &(*out)[i]);
173 IMMDevice_Release(dev);
175 }else
176 *out = NULL;
178 IMMDeviceCollection_Release(coll);
180 return TRUE;
183 static BOOL get_driver_name(IMMDeviceEnumerator *devenum, PROPVARIANT *pv)
185 IMMDevice *device;
186 IPropertyStore *ps;
187 HRESULT hr;
189 hr = IMMDeviceEnumerator_GetDevice(devenum, L"Wine info device", &device);
190 if(FAILED(hr))
191 return FALSE;
193 hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
194 if(FAILED(hr)){
195 IMMDevice_Release(device);
196 return FALSE;
199 hr = IPropertyStore_GetValue(ps,
200 (const PROPERTYKEY *)&DEVPKEY_Device_Driver, pv);
201 IPropertyStore_Release(ps);
202 IMMDevice_Release(device);
203 if(FAILED(hr))
204 return FALSE;
206 return TRUE;
209 static void initAudioDlg (HWND hDlg)
211 WCHAR display_str[256], format_str[256], sysdefault_str[256], disabled_str[64];
212 IMMDeviceEnumerator *devenum;
213 BOOL have_driver = FALSE;
214 HRESULT hr;
215 UINT i;
216 LVCOLUMNW lvcol;
217 WCHAR colW[64], speaker_str[256];
218 RECT rect;
219 DWORD width;
221 WINE_TRACE("\n");
223 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER, format_str, ARRAY_SIZE(format_str));
224 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER_NONE, disabled_str,
225 ARRAY_SIZE(disabled_str));
226 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SYSDEFAULT, sysdefault_str,
227 ARRAY_SIZE(sysdefault_str));
229 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
230 CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
231 if(SUCCEEDED(hr)){
232 PROPVARIANT pv;
234 load_devices(devenum, eRender, &num_render_devs, &render_devs);
235 load_devices(devenum, eCapture, &num_capture_devs, &capture_devs);
237 PropVariantInit(&pv);
238 if(get_driver_name(devenum, &pv) && pv.pwszVal[0] != '\0'){
239 have_driver = TRUE;
240 swprintf(display_str, ARRAY_SIZE(display_str), format_str, pv.pwszVal);
241 lstrcatW(g_drv_keyW, pv.pwszVal);
243 PropVariantClear(&pv);
245 IMMDeviceEnumerator_Release(devenum);
248 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
249 0, (LPARAM)sysdefault_str);
250 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, 0, 0);
251 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
252 0, (LPARAM)sysdefault_str);
253 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, 0, 0);
255 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
256 0, (LPARAM)sysdefault_str);
257 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, 0, 0);
258 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
259 0, (LPARAM)sysdefault_str);
260 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, 0, 0);
262 i = 0;
263 while (speaker_configs[i].text_id != 0) {
264 LoadStringW(GetModuleHandleW(NULL), speaker_configs[i].text_id, speaker_str,
265 ARRAY_SIZE(speaker_str));
267 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_ADDSTRING,
268 0, (LPARAM)speaker_str);
270 i++;
273 GetClientRect(GetDlgItem(hDlg, IDC_LIST_AUDIO_DEVICES), &rect);
274 width = (rect.right - rect.left) * 3 / 5;
276 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DEVICE, colW, ARRAY_SIZE(colW));
277 lvcol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
278 lvcol.pszText = colW;
279 lvcol.cchTextMax = lstrlenW(colW);
280 lvcol.cx = width;
281 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvcol);
283 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SPEAKER_CONFIG, colW, ARRAY_SIZE(colW));
284 lvcol.pszText = colW;
285 lvcol.cchTextMax = lstrlenW(colW);
286 lvcol.cx = rect.right - rect.left - width;
287 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvcol);
289 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
291 if(have_driver){
292 WCHAR *reg_out_dev, *reg_vout_dev, *reg_in_dev, *reg_vin_dev;
294 reg_out_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultOutput", NULL);
295 reg_vout_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceOutput", NULL);
296 reg_in_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultInput", NULL);
297 reg_vin_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceInput", NULL);
299 for(i = 0; i < num_render_devs; ++i){
300 LVITEMW lvitem;
302 if(!render_devs[i].id)
303 continue;
305 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
306 0, (LPARAM)render_devs[i].name.pwszVal);
307 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETITEMDATA,
308 i + 1, (LPARAM)&render_devs[i]);
310 if(reg_out_dev && !wcscmp(render_devs[i].id, reg_out_dev)){
311 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
312 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL, render_devs[i].speaker_config, 0);
315 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
316 0, (LPARAM)render_devs[i].name.pwszVal);
317 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETITEMDATA,
318 i + 1, (LPARAM)&render_devs[i]);
319 if(reg_vout_dev && !wcscmp(render_devs[i].id, reg_vout_dev))
320 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
322 lvitem.mask = LVIF_TEXT | LVIF_PARAM;
323 lvitem.iItem = i;
324 lvitem.iSubItem = 0;
325 lvitem.pszText = render_devs[i].name.pwszVal;
326 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
327 lvitem.lParam = (LPARAM)&render_devs[i];
329 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTITEMW, 0, (LPARAM)&lvitem);
331 LoadStringW(GetModuleHandleW(NULL), speaker_configs[render_devs[i].speaker_config].text_id,
332 speaker_str, ARRAY_SIZE(speaker_str));
334 lvitem.mask = LVIF_TEXT;
335 lvitem.iItem = i;
336 lvitem.iSubItem = 1;
337 lvitem.pszText = speaker_str;
338 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
340 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
343 for(i = 0; i < num_capture_devs; ++i){
344 if(!capture_devs[i].id)
345 continue;
347 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
348 0, (LPARAM)capture_devs[i].name.pwszVal);
349 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETITEMDATA,
350 i + 1, (LPARAM)&capture_devs[i]);
351 if(reg_in_dev && !wcscmp(capture_devs[i].id, reg_in_dev))
352 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, i + 1, 0);
354 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
355 0, (LPARAM)capture_devs[i].name.pwszVal);
356 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETITEMDATA,
357 i + 1, (LPARAM)&capture_devs[i]);
358 if(reg_vin_dev && !wcscmp(capture_devs[i].id, reg_vin_dev))
359 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, i + 1, 0);
362 HeapFree(GetProcessHeap(), 0, reg_out_dev);
363 HeapFree(GetProcessHeap(), 0, reg_vout_dev);
364 HeapFree(GetProcessHeap(), 0, reg_in_dev);
365 HeapFree(GetProcessHeap(), 0, reg_vin_dev);
366 }else
367 swprintf(display_str, ARRAY_SIZE(display_str), format_str, disabled_str);
369 SetDlgItemTextW(hDlg, IDC_AUDIO_DRIVER, display_str);
372 static void set_reg_device(HWND hDlg, int dlgitem, const WCHAR *key_name)
374 UINT idx;
375 struct DeviceInfo *info;
377 idx = SendDlgItemMessageW(hDlg, dlgitem, CB_GETCURSEL, 0, 0);
379 info = (struct DeviceInfo *)SendDlgItemMessageW(hDlg, dlgitem,
380 CB_GETITEMDATA, idx, 0);
382 if(!info || info == (void*)CB_ERR)
383 set_reg_key(HKEY_CURRENT_USER, g_drv_keyW, key_name, NULL);
384 else
385 set_reg_key(HKEY_CURRENT_USER, g_drv_keyW, key_name, info->id);
388 static void test_sound(void)
390 if(!PlaySoundW(MAKEINTRESOURCEW(IDW_TESTSOUND), NULL, SND_RESOURCE | SND_ASYNC)){
391 WCHAR error_str[256], title_str[256];
393 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED, error_str,
394 ARRAY_SIZE(error_str));
395 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED_TITLE, title_str,
396 ARRAY_SIZE(title_str));
398 MessageBoxW(NULL, error_str, title_str, MB_OK | MB_ICONERROR);
402 static void apply_speaker_configs(void)
404 UINT i;
405 IMMDeviceEnumerator *devenum;
406 IMMDevice *dev;
407 IPropertyStore *ps;
408 PROPVARIANT pv;
409 HRESULT hr;
411 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
412 CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
414 if(FAILED(hr)){
415 ERR("Unable to create MMDeviceEnumerator: 0x%08lx\n", hr);
416 return;
419 PropVariantInit(&pv);
420 pv.vt = VT_UI4;
422 for (i = 0; i < num_render_devs; i++) {
423 hr = IMMDeviceEnumerator_GetDevice(devenum, render_devs[i].id, &dev);
425 if(FAILED(hr)){
426 WARN("Could not get MMDevice for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
427 continue;
430 hr = IMMDevice_OpenPropertyStore(dev, STGM_WRITE, &ps);
432 if(FAILED(hr)){
433 WARN("Could not open property store for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
434 IMMDevice_Release(dev);
435 continue;
438 pv.ulVal = speaker_configs[render_devs[i].speaker_config].speaker_mask;
440 hr = IPropertyStore_SetValue(ps, &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);
442 if (FAILED(hr))
443 WARN("IPropertyStore_SetValue failed for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
445 IPropertyStore_Release(ps);
446 IMMDevice_Release(dev);
449 IMMDeviceEnumerator_Release(devenum);
452 static void listview_changed(HWND hDlg)
454 int idx;
456 idx = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
457 if(idx < 0) {
458 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
459 return;
462 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL,
463 render_devs[idx].speaker_config, 0);
465 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 1);
468 INT_PTR CALLBACK
469 AudioDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
471 switch (uMsg) {
472 case WM_COMMAND:
473 switch (LOWORD(wParam)) {
474 case IDC_AUDIO_TEST:
475 test_sound();
476 break;
477 case IDC_AUDIOOUT_DEVICE:
478 if(HIWORD(wParam) == CBN_SELCHANGE){
479 set_reg_device(hDlg, IDC_AUDIOOUT_DEVICE, L"DefaultOutput");
480 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
482 break;
483 case IDC_VOICEOUT_DEVICE:
484 if(HIWORD(wParam) == CBN_SELCHANGE){
485 set_reg_device(hDlg, IDC_VOICEOUT_DEVICE, L"DefaultVoiceOutput");
486 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
488 break;
489 case IDC_AUDIOIN_DEVICE:
490 if(HIWORD(wParam) == CBN_SELCHANGE){
491 set_reg_device(hDlg, IDC_AUDIOIN_DEVICE, L"DefaultInput");
492 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
494 break;
495 case IDC_VOICEIN_DEVICE:
496 if(HIWORD(wParam) == CBN_SELCHANGE){
497 set_reg_device(hDlg, IDC_VOICEIN_DEVICE, L"DefaultVoiceInput");
498 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
500 break;
501 case IDC_SPEAKERCONFIG_SPEAKERS:
502 if(HIWORD(wParam) == CBN_SELCHANGE){
503 UINT dev, idx;
505 idx = SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_GETCURSEL, 0, 0);
506 dev = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
508 if(dev < num_render_devs){
509 WCHAR speaker_str[256];
510 LVITEMW lvitem;
512 render_devs[dev].speaker_config = idx;
514 LoadStringW(GetModuleHandleW(NULL), speaker_configs[idx].text_id,
515 speaker_str, ARRAY_SIZE(speaker_str));
517 lvitem.mask = LVIF_TEXT;
518 lvitem.iItem = dev;
519 lvitem.iSubItem = 1;
520 lvitem.pszText = speaker_str;
521 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
523 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
525 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
528 break;
530 break;
532 case WM_SHOWWINDOW:
533 set_window_title(hDlg);
534 break;
536 case WM_NOTIFY:
537 switch(((LPNMHDR)lParam)->code) {
538 case PSN_KILLACTIVE:
539 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, FALSE);
540 break;
541 case PSN_APPLY:
542 apply_speaker_configs();
543 apply();
544 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
545 break;
546 case PSN_SETACTIVE:
547 break;
548 case LVN_ITEMCHANGED:
549 listview_changed(hDlg);
550 break;
552 break;
553 case WM_INITDIALOG:
554 initAudioDlg(hDlg);
555 break;
558 return FALSE;