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
26 #include "wine/port.h"
35 #include <wine/debug.h>
49 #include "mmdeviceapi.h"
50 #include "audioclient.h"
51 #include "audiopolicy.h"
56 WINE_DEFAULT_DEBUG_CHANNEL(winecfg
);
64 static WCHAR g_drv_keyW
[256] = {'S','o','f','t','w','a','r','e','\\',
65 'W','i','n','e','\\','D','r','i','v','e','r','s','\\',0};
67 static const WCHAR reg_out_nameW
[] = {'D','e','f','a','u','l','t','O','u','t','p','u','t',0};
68 static const WCHAR reg_in_nameW
[] = {'D','e','f','a','u','l','t','I','n','p','u','t',0};
69 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};
70 static const WCHAR reg_vin_nameW
[] = {'D','e','f','a','u','l','t','V','o','i','c','e','I','n','p','u','t',0};
72 static UINT num_render_devs
, num_capture_devs
;
73 static struct DeviceInfo
*render_devs
, *capture_devs
;
81 { IDS_AUDIO_SPEAKER_5POINT1
, KSAUDIO_SPEAKER_5POINT1
},
82 { IDS_AUDIO_SPEAKER_QUAD
, KSAUDIO_SPEAKER_QUAD
},
83 { IDS_AUDIO_SPEAKER_STEREO
, KSAUDIO_SPEAKER_STEREO
},
84 { IDS_AUDIO_SPEAKER_MONO
, KSAUDIO_SPEAKER_MONO
},
88 static BOOL
load_device(IMMDevice
*dev
, struct DeviceInfo
*info
)
95 hr
= IMMDevice_GetId(dev
, &info
->id
);
101 hr
= IMMDevice_OpenPropertyStore(dev
, STGM_READ
, &ps
);
103 CoTaskMemFree(info
->id
);
108 PropVariantInit(&info
->name
);
110 hr
= IPropertyStore_GetValue(ps
,
111 (PROPERTYKEY
*)&DEVPKEY_Device_FriendlyName
, &info
->name
);
113 CoTaskMemFree(info
->id
);
115 IPropertyStore_Release(ps
);
119 PropVariantInit(&pv
);
121 hr
= IPropertyStore_GetValue(ps
,
122 &PKEY_AudioEndpoint_PhysicalSpeakers
, &pv
);
124 info
->speaker_config
= -1;
125 if(SUCCEEDED(hr
) && pv
.vt
== VT_UI4
){
127 while (speaker_configs
[i
].text_id
!= 0) {
128 if ((speaker_configs
[i
].speaker_mask
& pv
.u
.ulVal
) == speaker_configs
[i
].speaker_mask
) {
129 info
->speaker_config
= i
;
136 /* fallback to stereo */
137 if(info
->speaker_config
== -1)
138 info
->speaker_config
= 2;
140 IPropertyStore_Release(ps
);
145 static BOOL
load_devices(IMMDeviceEnumerator
*devenum
, EDataFlow dataflow
,
146 UINT
*ndevs
, struct DeviceInfo
**out
)
148 IMMDeviceCollection
*coll
;
152 hr
= IMMDeviceEnumerator_EnumAudioEndpoints(devenum
, dataflow
,
153 DEVICE_STATE_ACTIVE
, &coll
);
157 hr
= IMMDeviceCollection_GetCount(coll
, ndevs
);
159 IMMDeviceCollection_Release(coll
);
164 *out
= HeapAlloc(GetProcessHeap(), 0,
165 sizeof(struct DeviceInfo
) * (*ndevs
));
167 IMMDeviceCollection_Release(coll
);
171 for(i
= 0; i
< *ndevs
; ++i
){
174 hr
= IMMDeviceCollection_Item(coll
, i
, &dev
);
180 load_device(dev
, &(*out
)[i
]);
182 IMMDevice_Release(dev
);
187 IMMDeviceCollection_Release(coll
);
192 static BOOL
get_driver_name(IMMDeviceEnumerator
*devenum
, PROPVARIANT
*pv
)
198 static const WCHAR wine_info_deviceW
[] = {'W','i','n','e',' ',
199 'i','n','f','o',' ','d','e','v','i','c','e',0};
201 hr
= IMMDeviceEnumerator_GetDevice(devenum
, wine_info_deviceW
, &device
);
205 hr
= IMMDevice_OpenPropertyStore(device
, STGM_READ
, &ps
);
207 IMMDevice_Release(device
);
211 hr
= IPropertyStore_GetValue(ps
,
212 (const PROPERTYKEY
*)&DEVPKEY_Device_Driver
, pv
);
213 IPropertyStore_Release(ps
);
214 IMMDevice_Release(device
);
221 static void initAudioDlg (HWND hDlg
)
223 WCHAR display_str
[256], format_str
[256], sysdefault_str
[256], disabled_str
[64];
224 IMMDeviceEnumerator
*devenum
;
225 BOOL have_driver
= FALSE
;
229 WCHAR colW
[64], speaker_str
[256];
235 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_DRIVER
,
236 format_str
, sizeof(format_str
) / sizeof(*format_str
));
237 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_DRIVER_NONE
,
238 disabled_str
, sizeof(disabled_str
) / sizeof(*disabled_str
));
239 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_SYSDEFAULT
,
240 sysdefault_str
, sizeof(sysdefault_str
) / sizeof(*sysdefault_str
));
242 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
,
243 CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, (void**)&devenum
);
247 load_devices(devenum
, eRender
, &num_render_devs
, &render_devs
);
248 load_devices(devenum
, eCapture
, &num_capture_devs
, &capture_devs
);
250 PropVariantInit(&pv
);
251 if(get_driver_name(devenum
, &pv
) && pv
.u
.pwszVal
[0] != '\0'){
253 wnsprintfW(display_str
, sizeof(display_str
) / sizeof(*display_str
),
254 format_str
, pv
.u
.pwszVal
);
255 lstrcatW(g_drv_keyW
, pv
.u
.pwszVal
);
257 PropVariantClear(&pv
);
259 IMMDeviceEnumerator_Release(devenum
);
262 SendDlgItemMessageW(hDlg
, IDC_AUDIOOUT_DEVICE
, CB_ADDSTRING
,
263 0, (LPARAM
)sysdefault_str
);
264 SendDlgItemMessageW(hDlg
, IDC_AUDIOOUT_DEVICE
, CB_SETCURSEL
, 0, 0);
265 SendDlgItemMessageW(hDlg
, IDC_VOICEOUT_DEVICE
, CB_ADDSTRING
,
266 0, (LPARAM
)sysdefault_str
);
267 SendDlgItemMessageW(hDlg
, IDC_VOICEOUT_DEVICE
, CB_SETCURSEL
, 0, 0);
269 SendDlgItemMessageW(hDlg
, IDC_AUDIOIN_DEVICE
, CB_ADDSTRING
,
270 0, (LPARAM
)sysdefault_str
);
271 SendDlgItemMessageW(hDlg
, IDC_AUDIOIN_DEVICE
, CB_SETCURSEL
, 0, 0);
272 SendDlgItemMessageW(hDlg
, IDC_VOICEIN_DEVICE
, CB_ADDSTRING
,
273 0, (LPARAM
)sysdefault_str
);
274 SendDlgItemMessageW(hDlg
, IDC_VOICEIN_DEVICE
, CB_SETCURSEL
, 0, 0);
277 while (speaker_configs
[i
].text_id
!= 0) {
278 LoadStringW(GetModuleHandleW(NULL
), speaker_configs
[i
].text_id
,
279 speaker_str
, sizeof(speaker_str
) / sizeof(*speaker_str
));
281 SendDlgItemMessageW(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
, CB_ADDSTRING
,
282 0, (LPARAM
)speaker_str
);
287 GetClientRect(GetDlgItem(hDlg
, IDC_LIST_AUDIO_DEVICES
), &rect
);
288 width
= (rect
.right
- rect
.left
) * 3 / 5;
290 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_DEVICE
, colW
, sizeof(colW
)/sizeof(*colW
));
291 lvcol
.mask
= LVCF_TEXT
| LVCF_WIDTH
| LVCF_SUBITEM
;
292 lvcol
.pszText
= colW
;
293 lvcol
.cchTextMax
= lstrlenW(colW
);
295 SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_INSERTCOLUMNW
, 0, (LPARAM
)&lvcol
);
297 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_SPEAKER_CONFIG
, colW
, sizeof(colW
)/sizeof(*colW
));
298 lvcol
.pszText
= colW
;
299 lvcol
.cchTextMax
= lstrlenW(colW
);
300 lvcol
.cx
= rect
.right
- rect
.left
- width
;
301 SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_INSERTCOLUMNW
, 1, (LPARAM
)&lvcol
);
303 EnableWindow(GetDlgItem(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
), 0);
306 WCHAR
*reg_out_dev
, *reg_vout_dev
, *reg_in_dev
, *reg_vin_dev
;
308 reg_out_dev
= get_reg_keyW(HKEY_CURRENT_USER
, g_drv_keyW
, reg_out_nameW
, NULL
);
309 reg_vout_dev
= get_reg_keyW(HKEY_CURRENT_USER
, g_drv_keyW
, reg_vout_nameW
, NULL
);
310 reg_in_dev
= get_reg_keyW(HKEY_CURRENT_USER
, g_drv_keyW
, reg_in_nameW
, NULL
);
311 reg_vin_dev
= get_reg_keyW(HKEY_CURRENT_USER
, g_drv_keyW
, reg_vin_nameW
, NULL
);
313 for(i
= 0; i
< num_render_devs
; ++i
){
316 if(!render_devs
[i
].id
)
319 SendDlgItemMessageW(hDlg
, IDC_AUDIOOUT_DEVICE
, CB_ADDSTRING
,
320 0, (LPARAM
)render_devs
[i
].name
.u
.pwszVal
);
321 SendDlgItemMessageW(hDlg
, IDC_AUDIOOUT_DEVICE
, CB_SETITEMDATA
,
322 i
+ 1, (LPARAM
)&render_devs
[i
]);
324 if(reg_out_dev
&& !lstrcmpW(render_devs
[i
].id
, reg_out_dev
)){
325 SendDlgItemMessageW(hDlg
, IDC_AUDIOOUT_DEVICE
, CB_SETCURSEL
, i
+ 1, 0);
326 SendDlgItemMessageW(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
, CB_SETCURSEL
, render_devs
[i
].speaker_config
, 0);
329 SendDlgItemMessageW(hDlg
, IDC_VOICEOUT_DEVICE
, CB_ADDSTRING
,
330 0, (LPARAM
)render_devs
[i
].name
.u
.pwszVal
);
331 SendDlgItemMessageW(hDlg
, IDC_VOICEOUT_DEVICE
, CB_SETITEMDATA
,
332 i
+ 1, (LPARAM
)&render_devs
[i
]);
333 if(reg_vout_dev
&& !lstrcmpW(render_devs
[i
].id
, reg_vout_dev
))
334 SendDlgItemMessageW(hDlg
, IDC_VOICEOUT_DEVICE
, CB_SETCURSEL
, i
+ 1, 0);
336 lvitem
.mask
= LVIF_TEXT
| LVIF_PARAM
;
339 lvitem
.pszText
= render_devs
[i
].name
.u
.pwszVal
;
340 lvitem
.cchTextMax
= lstrlenW(lvitem
.pszText
);
341 lvitem
.lParam
= (LPARAM
)&render_devs
[i
];
343 SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_INSERTITEMW
, 0, (LPARAM
)&lvitem
);
345 LoadStringW(GetModuleHandleW(NULL
), speaker_configs
[render_devs
[i
].speaker_config
].text_id
,
346 speaker_str
, sizeof(speaker_str
) / sizeof(*speaker_str
));
348 lvitem
.mask
= LVIF_TEXT
;
351 lvitem
.pszText
= speaker_str
;
352 lvitem
.cchTextMax
= lstrlenW(lvitem
.pszText
);
354 SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_SETITEMW
, 0, (LPARAM
)&lvitem
);
357 for(i
= 0; i
< num_capture_devs
; ++i
){
358 if(!capture_devs
[i
].id
)
361 SendDlgItemMessageW(hDlg
, IDC_AUDIOIN_DEVICE
, CB_ADDSTRING
,
362 0, (LPARAM
)capture_devs
[i
].name
.u
.pwszVal
);
363 SendDlgItemMessageW(hDlg
, IDC_AUDIOIN_DEVICE
, CB_SETITEMDATA
,
364 i
+ 1, (LPARAM
)&capture_devs
[i
]);
365 if(reg_in_dev
&& !lstrcmpW(capture_devs
[i
].id
, reg_in_dev
))
366 SendDlgItemMessageW(hDlg
, IDC_AUDIOIN_DEVICE
, CB_SETCURSEL
, i
+ 1, 0);
368 SendDlgItemMessageW(hDlg
, IDC_VOICEIN_DEVICE
, CB_ADDSTRING
,
369 0, (LPARAM
)capture_devs
[i
].name
.u
.pwszVal
);
370 SendDlgItemMessageW(hDlg
, IDC_VOICEIN_DEVICE
, CB_SETITEMDATA
,
371 i
+ 1, (LPARAM
)&capture_devs
[i
]);
372 if(reg_vin_dev
&& !lstrcmpW(capture_devs
[i
].id
, reg_vin_dev
))
373 SendDlgItemMessageW(hDlg
, IDC_VOICEIN_DEVICE
, CB_SETCURSEL
, i
+ 1, 0);
376 HeapFree(GetProcessHeap(), 0, reg_out_dev
);
377 HeapFree(GetProcessHeap(), 0, reg_vout_dev
);
378 HeapFree(GetProcessHeap(), 0, reg_in_dev
);
379 HeapFree(GetProcessHeap(), 0, reg_vin_dev
);
381 wnsprintfW(display_str
, sizeof(display_str
) / sizeof(*display_str
),
382 format_str
, disabled_str
);
384 SetDlgItemTextW(hDlg
, IDC_AUDIO_DRIVER
, display_str
);
387 static void set_reg_device(HWND hDlg
, int dlgitem
, const WCHAR
*key_name
)
390 struct DeviceInfo
*info
;
392 idx
= SendDlgItemMessageW(hDlg
, dlgitem
, CB_GETCURSEL
, 0, 0);
394 info
= (struct DeviceInfo
*)SendDlgItemMessageW(hDlg
, dlgitem
,
395 CB_GETITEMDATA
, idx
, 0);
397 if(!info
|| info
== (void*)CB_ERR
)
398 set_reg_keyW(HKEY_CURRENT_USER
, g_drv_keyW
, key_name
, NULL
);
400 set_reg_keyW(HKEY_CURRENT_USER
, g_drv_keyW
, key_name
, info
->id
);
403 static void test_sound(void)
405 if(!PlaySoundW(MAKEINTRESOURCEW(IDW_TESTSOUND
), NULL
, SND_RESOURCE
| SND_ASYNC
)){
406 WCHAR error_str
[256], title_str
[256];
408 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_TEST_FAILED
,
409 error_str
, sizeof(error_str
) / sizeof(*error_str
));
410 LoadStringW(GetModuleHandleW(NULL
), IDS_AUDIO_TEST_FAILED_TITLE
,
411 title_str
, sizeof(title_str
) / sizeof(*title_str
));
413 MessageBoxW(NULL
, error_str
, title_str
, MB_OK
| MB_ICONERROR
);
417 static void apply_speaker_configs(void)
420 IMMDeviceEnumerator
*devenum
;
426 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
,
427 CLSCTX_INPROC_SERVER
, &IID_IMMDeviceEnumerator
, (void**)&devenum
);
430 ERR("Unable to create MMDeviceEnumerator: 0x%08x\n", hr
);
434 PropVariantInit(&pv
);
437 for (i
= 0; i
< num_render_devs
; i
++) {
438 hr
= IMMDeviceEnumerator_GetDevice(devenum
, render_devs
[i
].id
, &dev
);
441 WARN("Could not get MMDevice for %s: 0x%08x\n", wine_dbgstr_w(render_devs
[i
].id
), hr
);
445 hr
= IMMDevice_OpenPropertyStore(dev
, STGM_WRITE
, &ps
);
448 WARN("Could not open property store for %s: 0x%08x\n", wine_dbgstr_w(render_devs
[i
].id
), hr
);
449 IMMDevice_Release(dev
);
453 pv
.u
.ulVal
= speaker_configs
[render_devs
[i
].speaker_config
].speaker_mask
;
455 hr
= IPropertyStore_SetValue(ps
, &PKEY_AudioEndpoint_PhysicalSpeakers
, &pv
);
458 WARN("IPropertyStore_SetValue failed for %s: 0x%08x\n", wine_dbgstr_w(render_devs
[i
].id
), hr
);
460 IPropertyStore_Release(ps
);
461 IMMDevice_Release(dev
);
464 IMMDeviceEnumerator_Release(devenum
);
467 static void listview_changed(HWND hDlg
)
471 idx
= SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_GETNEXTITEM
, -1, LVNI_SELECTED
);
473 EnableWindow(GetDlgItem(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
), 0);
477 SendDlgItemMessageW(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
, CB_SETCURSEL
,
478 render_devs
[idx
].speaker_config
, 0);
480 EnableWindow(GetDlgItem(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
), 1);
484 AudioDlgProc (HWND hDlg
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
488 switch (LOWORD(wParam
)) {
492 case IDC_AUDIOOUT_DEVICE
:
493 if(HIWORD(wParam
) == CBN_SELCHANGE
){
494 set_reg_device(hDlg
, IDC_AUDIOOUT_DEVICE
, reg_out_nameW
);
495 SendMessageW(GetParent(hDlg
), PSM_CHANGED
, 0, 0);
498 case IDC_VOICEOUT_DEVICE
:
499 if(HIWORD(wParam
) == CBN_SELCHANGE
){
500 set_reg_device(hDlg
, IDC_VOICEOUT_DEVICE
, reg_vout_nameW
);
501 SendMessageW(GetParent(hDlg
), PSM_CHANGED
, 0, 0);
504 case IDC_AUDIOIN_DEVICE
:
505 if(HIWORD(wParam
) == CBN_SELCHANGE
){
506 set_reg_device(hDlg
, IDC_AUDIOIN_DEVICE
, reg_in_nameW
);
507 SendMessageW(GetParent(hDlg
), PSM_CHANGED
, 0, 0);
510 case IDC_VOICEIN_DEVICE
:
511 if(HIWORD(wParam
) == CBN_SELCHANGE
){
512 set_reg_device(hDlg
, IDC_VOICEIN_DEVICE
, reg_vin_nameW
);
513 SendMessageW(GetParent(hDlg
), PSM_CHANGED
, 0, 0);
516 case IDC_SPEAKERCONFIG_SPEAKERS
:
517 if(HIWORD(wParam
) == CBN_SELCHANGE
){
520 idx
= SendDlgItemMessageW(hDlg
, IDC_SPEAKERCONFIG_SPEAKERS
, CB_GETCURSEL
, 0, 0);
521 dev
= SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_GETNEXTITEM
, -1, LVNI_SELECTED
);
523 if(dev
< num_render_devs
){
524 WCHAR speaker_str
[256];
527 render_devs
[dev
].speaker_config
= idx
;
529 LoadStringW(GetModuleHandleW(NULL
), speaker_configs
[idx
].text_id
,
530 speaker_str
, sizeof(speaker_str
) / sizeof(*speaker_str
));
532 lvitem
.mask
= LVIF_TEXT
;
535 lvitem
.pszText
= speaker_str
;
536 lvitem
.cchTextMax
= lstrlenW(lvitem
.pszText
);
538 SendDlgItemMessageW(hDlg
, IDC_LIST_AUDIO_DEVICES
, LVM_SETITEMW
, 0, (LPARAM
)&lvitem
);
540 SendMessageW(GetParent(hDlg
), PSM_CHANGED
, 0, 0);
548 set_window_title(hDlg
);
552 switch(((LPNMHDR
)lParam
)->code
) {
554 SetWindowLongPtrW(hDlg
, DWLP_MSGRESULT
, FALSE
);
557 apply_speaker_configs();
559 SetWindowLongPtrW(hDlg
, DWLP_MSGRESULT
, PSNRET_NOERROR
);
563 case LVN_ITEMCHANGED
:
564 listview_changed(hDlg
);