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
30 #include <wine/debug.h>
43 #include "propkeydef.h"
45 #include "mmdeviceapi.h"
46 #include "audioclient.h"
47 #include "audiopolicy.h"
52 WINE_DEFAULT_DEBUG_CHANNEL(winecfg
);
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
;
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
},
78 static BOOL
load_device(IMMDevice
*dev
, struct DeviceInfo
*info
)
85 hr
= IMMDevice_GetId(dev
, &info
->id
);
91 hr
= IMMDevice_OpenPropertyStore(dev
, STGM_READ
, &ps
);
93 CoTaskMemFree(info
->id
);
98 PropVariantInit(&info
->name
);
100 hr
= IPropertyStore_GetValue(ps
,
101 (PROPERTYKEY
*)&DEVPKEY_Device_FriendlyName
, &info
->name
);
103 CoTaskMemFree(info
->id
);
105 IPropertyStore_Release(ps
);
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
){
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
;
126 /* fallback to stereo */
127 if(info
->speaker_config
== -1)
128 info
->speaker_config
= 2;
130 IPropertyStore_Release(ps
);
135 static BOOL
load_devices(IMMDeviceEnumerator
*devenum
, EDataFlow dataflow
,
136 UINT
*ndevs
, struct DeviceInfo
**out
)
138 IMMDeviceCollection
*coll
;
142 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(devenum
, dataflow
,
143 DEVICE_STATE_ACTIVE
, &coll
);
147 hr
= IMMDeviceCollection_GetCount(coll
, ndevs
);
149 IMMDeviceCollection_Release(coll
);
154 *out
= malloc(sizeof(struct DeviceInfo
) * (*ndevs
));
156 IMMDeviceCollection_Release(coll
);
160 for(i
= 0; i
< *ndevs
; ++i
){
163 hr
= IMMDeviceCollection_Item(coll
, i
, &dev
);
169 load_device(dev
, &(*out
)[i
]);
171 IMMDevice_Release(dev
);
176 IMMDeviceCollection_Release(coll
);
181 static BOOL
get_driver_name(IMMDeviceEnumerator
*devenum
, PROPVARIANT
*pv
)
187 hr
= IMMDeviceEnumerator_GetDevice(devenum
, L
"Wine info device", &device
);
191 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
193 IMMDevice_Release(device
);
197 hr
= IPropertyStore_GetValue(ps
,
198 (const PROPERTYKEY
*)&DEVPKEY_Device_Driver
, pv
);
199 IPropertyStore_Release(ps
);
200 IMMDevice_Release(device
);
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
;
215 WCHAR colW
[64], speaker_str
[256];
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
);
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'){
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);
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
);
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
);
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);
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
){
300 if(!render_devs
[i
].id
)
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
;
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
;
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
)
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);
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
)
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
);
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)
403 IMMDeviceEnumerator
*devenum
;
409 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
,
410 CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, (void**)&devenum
);
413 ERR("Unable to create MMDeviceEnumerator: 0x%08lx\n", hr
);
417 PropVariantInit(&pv
);
420 for (i
= 0; i
< num_render_devs
; i
++) {
421 hr
= IMMDeviceEnumerator_GetDevice(devenum
, render_devs
[i
].id
, &dev
);
424 WARN("Could not get MMDevice for %s: 0x%08lx\n", wine_dbgstr_w(render_devs
[i
].id
), hr
);
428 hr
= IMMDevice_OpenPropertyStore(dev
, STGM_WRITE
, &ps
);
431 WARN("Could not open property store for %s: 0x%08lx\n", wine_dbgstr_w(render_devs
[i
].id
), hr
);
432 IMMDevice_Release(dev
);
436 pv
.ulVal
= speaker_configs
[render_devs
[i
].speaker_config
].speaker_mask
;
438 hr
= IPropertyStore_SetValue(ps
, &PKEY_AudioEndpoint_PhysicalSpeakers
, &pv
);
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
)
454 idx
= SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_GETNEXTITEM
, -1, LVNI_SELECTED
);
456 EnableWindow(GetDlgItem(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
), 0);
460 SendDlgItemMessageW(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
, CB_SETCURSEL
,
461 render_devs
[idx
].speaker_config
, 0);
463 EnableWindow(GetDlgItem(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
), 1);
467 AudioDlgProc (HWND hDlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
471 switch (LOWORD(wParam
)) {
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);
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);
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);
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);
499 case IDC_SPEAKERCONFIG_SPEAKERS
:
500 if(HIWORD(wParam
) == CBN_SELCHANGE
){
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];
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
;
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);
531 set_window_title(hDlg
);
535 switch(((LPNMHDR
)lParam
)->code
) {
537 SetWindowLongPtrW(hDlg
, DWLP_MSGRESULT
, FALSE
);
540 apply_speaker_configs();
542 SetWindowLongPtrW(hDlg
, DWLP_MSGRESULT
, PSNRET_NOERROR
);
546 case LVN_ITEMCHANGED
:
547 listview_changed(hDlg
);