mfplat: Only convert MEDIASUBTYPE for the formats which need it.
[wine.git] / programs / winecfg / audio.c
bloba5065ee5c21b217ee4fba298de7d8639c4f292c3
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 <stdio.h>
27 #include <string.h>
29 #define COBJMACROS
30 #include <windows.h>
31 #include <wine/debug.h>
32 #include <shellapi.h>
33 #include <objbase.h>
34 #include <shlguid.h>
35 #include <shlwapi.h>
36 #include <shlobj.h>
37 #include <mmsystem.h>
38 #include <mmreg.h>
39 #include <mmddk.h>
41 #include "ole2.h"
42 #include "propkey.h"
43 #include "initguid.h"
44 #include "propkeydef.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 = malloc(sizeof(struct DeviceInfo) * (*ndevs));
156 if(!*out){
157 IMMDeviceCollection_Release(coll);
158 return FALSE;
161 for(i = 0; i < *ndevs; ++i){
162 IMMDevice *dev;
164 hr = IMMDeviceCollection_Item(coll, i, &dev);
165 if(FAILED(hr)){
166 (*out)[i].id = NULL;
167 continue;
170 load_device(dev, &(*out)[i]);
172 IMMDevice_Release(dev);
174 }else
175 *out = NULL;
177 IMMDeviceCollection_Release(coll);
179 return TRUE;
182 static BOOL get_driver_name(IMMDeviceEnumerator *devenum, PROPVARIANT *pv)
184 IMMDevice *device;
185 IPropertyStore *ps;
186 HRESULT hr;
188 hr = IMMDeviceEnumerator_GetDevice(devenum, L"Wine info device", &device);
189 if(FAILED(hr))
190 return FALSE;
192 hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
193 if(FAILED(hr)){
194 IMMDevice_Release(device);
195 return FALSE;
198 hr = IPropertyStore_GetValue(ps,
199 (const PROPERTYKEY *)&DEVPKEY_Device_Driver, pv);
200 IPropertyStore_Release(ps);
201 IMMDevice_Release(device);
202 if(FAILED(hr))
203 return FALSE;
205 return TRUE;
208 static void initAudioDlg (HWND hDlg)
210 WCHAR display_str[256], format_str[256], sysdefault_str[256], disabled_str[64];
211 IMMDeviceEnumerator *devenum;
212 BOOL have_driver = FALSE;
213 HRESULT hr;
214 UINT i;
215 LVCOLUMNW lvcol;
216 WCHAR colW[64], speaker_str[256];
217 RECT rect;
218 DWORD width;
220 WINE_TRACE("\n");
222 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER, format_str, ARRAY_SIZE(format_str));
223 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER_NONE, disabled_str,
224 ARRAY_SIZE(disabled_str));
225 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SYSDEFAULT, sysdefault_str,
226 ARRAY_SIZE(sysdefault_str));
228 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
229 CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
230 if(SUCCEEDED(hr)){
231 PROPVARIANT pv;
233 load_devices(devenum, eRender, &num_render_devs, &render_devs);
234 load_devices(devenum, eCapture, &num_capture_devs, &capture_devs);
236 PropVariantInit(&pv);
237 if(get_driver_name(devenum, &pv) && pv.pwszVal[0] != '\0'){
238 have_driver = TRUE;
239 swprintf(display_str, ARRAY_SIZE(display_str), format_str, pv.pwszVal);
240 lstrcatW(g_drv_keyW, pv.pwszVal);
242 PropVariantClear(&pv);
244 IMMDeviceEnumerator_Release(devenum);
247 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
248 0, (LPARAM)sysdefault_str);
249 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, 0, 0);
250 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
251 0, (LPARAM)sysdefault_str);
252 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, 0, 0);
254 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
255 0, (LPARAM)sysdefault_str);
256 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, 0, 0);
257 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
258 0, (LPARAM)sysdefault_str);
259 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, 0, 0);
261 i = 0;
262 while (speaker_configs[i].text_id != 0) {
263 LoadStringW(GetModuleHandleW(NULL), speaker_configs[i].text_id, speaker_str,
264 ARRAY_SIZE(speaker_str));
266 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_ADDSTRING,
267 0, (LPARAM)speaker_str);
269 i++;
272 GetClientRect(GetDlgItem(hDlg, IDC_LIST_AUDIO_DEVICES), &rect);
273 width = (rect.right - rect.left) * 3 / 5;
275 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DEVICE, colW, ARRAY_SIZE(colW));
276 lvcol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
277 lvcol.pszText = colW;
278 lvcol.cchTextMax = lstrlenW(colW);
279 lvcol.cx = width;
280 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvcol);
282 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SPEAKER_CONFIG, colW, ARRAY_SIZE(colW));
283 lvcol.pszText = colW;
284 lvcol.cchTextMax = lstrlenW(colW);
285 lvcol.cx = rect.right - rect.left - width;
286 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvcol);
288 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
290 if(have_driver){
291 WCHAR *reg_out_dev, *reg_vout_dev, *reg_in_dev, *reg_vin_dev;
293 reg_out_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultOutput", NULL);
294 reg_vout_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceOutput", NULL);
295 reg_in_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultInput", NULL);
296 reg_vin_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceInput", NULL);
298 for(i = 0; i < num_render_devs; ++i){
299 LVITEMW lvitem;
301 if(!render_devs[i].id)
302 continue;
304 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
305 0, (LPARAM)render_devs[i].name.pwszVal);
306 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETITEMDATA,
307 i + 1, (LPARAM)&render_devs[i]);
309 if(reg_out_dev && !wcscmp(render_devs[i].id, reg_out_dev)){
310 SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
311 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL, render_devs[i].speaker_config, 0);
314 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
315 0, (LPARAM)render_devs[i].name.pwszVal);
316 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETITEMDATA,
317 i + 1, (LPARAM)&render_devs[i]);
318 if(reg_vout_dev && !wcscmp(render_devs[i].id, reg_vout_dev))
319 SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, i + 1, 0);
321 lvitem.mask = LVIF_TEXT | LVIF_PARAM;
322 lvitem.iItem = i;
323 lvitem.iSubItem = 0;
324 lvitem.pszText = render_devs[i].name.pwszVal;
325 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
326 lvitem.lParam = (LPARAM)&render_devs[i];
328 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTITEMW, 0, (LPARAM)&lvitem);
330 LoadStringW(GetModuleHandleW(NULL), speaker_configs[render_devs[i].speaker_config].text_id,
331 speaker_str, ARRAY_SIZE(speaker_str));
333 lvitem.mask = LVIF_TEXT;
334 lvitem.iItem = i;
335 lvitem.iSubItem = 1;
336 lvitem.pszText = speaker_str;
337 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
339 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
342 for(i = 0; i < num_capture_devs; ++i){
343 if(!capture_devs[i].id)
344 continue;
346 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
347 0, (LPARAM)capture_devs[i].name.pwszVal);
348 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETITEMDATA,
349 i + 1, (LPARAM)&capture_devs[i]);
350 if(reg_in_dev && !wcscmp(capture_devs[i].id, reg_in_dev))
351 SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, i + 1, 0);
353 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
354 0, (LPARAM)capture_devs[i].name.pwszVal);
355 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETITEMDATA,
356 i + 1, (LPARAM)&capture_devs[i]);
357 if(reg_vin_dev && !wcscmp(capture_devs[i].id, reg_vin_dev))
358 SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, i + 1, 0);
361 free(reg_out_dev);
362 free(reg_vout_dev);
363 free(reg_in_dev);
364 free(reg_vin_dev);
365 }else
366 swprintf(display_str, ARRAY_SIZE(display_str), format_str, disabled_str);
368 SetDlgItemTextW(hDlg, IDC_AUDIO_DRIVER, display_str);
371 static void set_reg_device(HWND hDlg, int dlgitem, const WCHAR *key_name)
373 UINT idx;
374 struct DeviceInfo *info;
376 idx = SendDlgItemMessageW(hDlg, dlgitem, CB_GETCURSEL, 0, 0);
378 info = (struct DeviceInfo *)SendDlgItemMessageW(hDlg, dlgitem,
379 CB_GETITEMDATA, idx, 0);
381 if(!info || info == (void*)CB_ERR)
382 set_reg_key(HKEY_CURRENT_USER, g_drv_keyW, key_name, NULL);
383 else
384 set_reg_key(HKEY_CURRENT_USER, g_drv_keyW, key_name, info->id);
387 static void test_sound(void)
389 if(!PlaySoundW(MAKEINTRESOURCEW(IDW_TESTSOUND), NULL, SND_RESOURCE | SND_ASYNC)){
390 WCHAR error_str[256], title_str[256];
392 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED, error_str,
393 ARRAY_SIZE(error_str));
394 LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED_TITLE, title_str,
395 ARRAY_SIZE(title_str));
397 MessageBoxW(NULL, error_str, title_str, MB_OK | MB_ICONERROR);
401 static void apply_speaker_configs(void)
403 UINT i;
404 IMMDeviceEnumerator *devenum;
405 IMMDevice *dev;
406 IPropertyStore *ps;
407 PROPVARIANT pv;
408 HRESULT hr;
410 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
411 CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
413 if(FAILED(hr)){
414 ERR("Unable to create MMDeviceEnumerator: 0x%08lx\n", hr);
415 return;
418 PropVariantInit(&pv);
419 pv.vt = VT_UI4;
421 for (i = 0; i < num_render_devs; i++) {
422 hr = IMMDeviceEnumerator_GetDevice(devenum, render_devs[i].id, &dev);
424 if(FAILED(hr)){
425 WARN("Could not get MMDevice for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
426 continue;
429 hr = IMMDevice_OpenPropertyStore(dev, STGM_WRITE, &ps);
431 if(FAILED(hr)){
432 WARN("Could not open property store for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
433 IMMDevice_Release(dev);
434 continue;
437 pv.ulVal = speaker_configs[render_devs[i].speaker_config].speaker_mask;
439 hr = IPropertyStore_SetValue(ps, &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);
441 if (FAILED(hr))
442 WARN("IPropertyStore_SetValue failed for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
444 IPropertyStore_Release(ps);
445 IMMDevice_Release(dev);
448 IMMDeviceEnumerator_Release(devenum);
451 static void listview_changed(HWND hDlg)
453 int idx;
455 idx = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
456 if(idx < 0) {
457 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
458 return;
461 SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL,
462 render_devs[idx].speaker_config, 0);
464 EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 1);
467 INT_PTR CALLBACK
468 AudioDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
470 switch (uMsg) {
471 case WM_COMMAND:
472 switch (LOWORD(wParam)) {
473 case IDC_AUDIO_TEST:
474 test_sound();
475 break;
476 case IDC_AUDIOOUT_DEVICE:
477 if(HIWORD(wParam) == CBN_SELCHANGE){
478 set_reg_device(hDlg, IDC_AUDIOOUT_DEVICE, L"DefaultOutput");
479 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
481 break;
482 case IDC_VOICEOUT_DEVICE:
483 if(HIWORD(wParam) == CBN_SELCHANGE){
484 set_reg_device(hDlg, IDC_VOICEOUT_DEVICE, L"DefaultVoiceOutput");
485 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
487 break;
488 case IDC_AUDIOIN_DEVICE:
489 if(HIWORD(wParam) == CBN_SELCHANGE){
490 set_reg_device(hDlg, IDC_AUDIOIN_DEVICE, L"DefaultInput");
491 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
493 break;
494 case IDC_VOICEIN_DEVICE:
495 if(HIWORD(wParam) == CBN_SELCHANGE){
496 set_reg_device(hDlg, IDC_VOICEIN_DEVICE, L"DefaultVoiceInput");
497 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
499 break;
500 case IDC_SPEAKERCONFIG_SPEAKERS:
501 if(HIWORD(wParam) == CBN_SELCHANGE){
502 UINT dev, idx;
504 idx = SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_GETCURSEL, 0, 0);
505 dev = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
507 if(dev < num_render_devs){
508 WCHAR speaker_str[256];
509 LVITEMW lvitem;
511 render_devs[dev].speaker_config = idx;
513 LoadStringW(GetModuleHandleW(NULL), speaker_configs[idx].text_id,
514 speaker_str, ARRAY_SIZE(speaker_str));
516 lvitem.mask = LVIF_TEXT;
517 lvitem.iItem = dev;
518 lvitem.iSubItem = 1;
519 lvitem.pszText = speaker_str;
520 lvitem.cchTextMax = lstrlenW(lvitem.pszText);
522 SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
524 SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
527 break;
529 break;
531 case WM_SHOWWINDOW:
532 set_window_title(hDlg);
533 break;
535 case WM_NOTIFY:
536 switch(((LPNMHDR)lParam)->code) {
537 case PSN_KILLACTIVE:
538 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, FALSE);
539 break;
540 case PSN_APPLY:
541 apply_speaker_configs();
542 apply();
543 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
544 break;
545 case PSN_SETACTIVE:
546 break;
547 case LVN_ITEMCHANGED:
548 listview_changed(hDlg);
549 break;
551 break;
552 case WM_INITDIALOG:
553 initAudioDlg(hDlg);
554 break;
557 return FALSE;