audio.c 17.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Audio management UI code
 *
 * Copyright 2004 Chris Morgan
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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
19 20 21
 *
 */

22
#define WIN32_LEAN_AND_MEAN
23 24
#define NONAMELESSUNION

25 26 27 28 29
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

30
#define COBJMACROS
31
#include <windows.h>
32 33 34 35 36 37
#include <wine/debug.h>
#include <shellapi.h>
#include <objbase.h>
#include <shlguid.h>
#include <shlwapi.h>
#include <shlobj.h>
38
#include <mmsystem.h>
39 40
#include <mmreg.h>
#include <mmddk.h>
41

42 43
#include "ole2.h"
#include "initguid.h"
44
#include "propkey.h"
45 46 47 48 49
#include "devpkey.h"
#include "mmdeviceapi.h"
#include "audioclient.h"
#include "audiopolicy.h"

50 51 52 53 54
#include "winecfg.h"
#include "resource.h"

WINE_DEFAULT_DEBUG_CHANNEL(winecfg);

55 56 57
struct DeviceInfo {
    WCHAR *id;
    PROPVARIANT name;
58
    int speaker_config;
59 60
};

61
static WCHAR g_drv_keyW[256] = L"Software\\Wine\\Drivers\\";
62 63 64 65

static UINT num_render_devs, num_capture_devs;
static struct DeviceInfo *render_devs, *capture_devs;

66 67 68 69 70 71 72 73 74 75 76 77 78
static const struct
{
    int text_id;
    DWORD speaker_mask;
} speaker_configs[] =
{
    { IDS_AUDIO_SPEAKER_5POINT1, KSAUDIO_SPEAKER_5POINT1 },
    { IDS_AUDIO_SPEAKER_QUAD, KSAUDIO_SPEAKER_QUAD },
    { IDS_AUDIO_SPEAKER_STEREO, KSAUDIO_SPEAKER_STEREO },
    { IDS_AUDIO_SPEAKER_MONO, KSAUDIO_SPEAKER_MONO },
    { 0, 0 }
};

79
static BOOL load_device(IMMDevice *dev, struct DeviceInfo *info)
80
{
81 82
    IPropertyStore *ps;
    HRESULT hr;
83 84
    PROPVARIANT pv;
    UINT i;
85

86 87 88 89 90
    hr = IMMDevice_GetId(dev, &info->id);
    if(FAILED(hr)){
        info->id = NULL;
        return FALSE;
    }
91

92 93 94 95 96 97 98 99 100 101 102 103 104 105
    hr = IMMDevice_OpenPropertyStore(dev, STGM_READ, &ps);
    if(FAILED(hr)){
        CoTaskMemFree(info->id);
        info->id = NULL;
        return FALSE;
    }

    PropVariantInit(&info->name);

    hr = IPropertyStore_GetValue(ps,
            (PROPERTYKEY*)&DEVPKEY_Device_FriendlyName, &info->name);
    if(FAILED(hr)){
        CoTaskMemFree(info->id);
        info->id = NULL;
106
        IPropertyStore_Release(ps);
107 108 109
        return FALSE;
    }

110 111 112 113 114
    PropVariantInit(&pv);

    hr = IPropertyStore_GetValue(ps,
            &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);

115
    info->speaker_config = -1;
116 117 118
    if(SUCCEEDED(hr) && pv.vt == VT_UI4){
        i = 0;
        while (speaker_configs[i].text_id != 0) {
119
            if ((speaker_configs[i].speaker_mask & pv.ulVal) == speaker_configs[i].speaker_mask) {
120 121 122 123 124 125 126 127
                info->speaker_config = i;
                break;
            }
            i++;
        }
    }

    /* fallback to stereo */
128
    if(info->speaker_config == -1)
129 130 131 132
        info->speaker_config = 2;

    IPropertyStore_Release(ps);

133 134 135 136 137 138 139 140 141 142 143 144
    return TRUE;
}

static BOOL load_devices(IMMDeviceEnumerator *devenum, EDataFlow dataflow,
        UINT *ndevs, struct DeviceInfo **out)
{
    IMMDeviceCollection *coll;
    UINT i;
    HRESULT hr;

    hr = IMMDeviceEnumerator_EnumAudioEndpoints(devenum, dataflow,
            DEVICE_STATE_ACTIVE, &coll);
145 146 147
    if(FAILED(hr))
        return FALSE;

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    hr = IMMDeviceCollection_GetCount(coll, ndevs);
    if(FAILED(hr)){
        IMMDeviceCollection_Release(coll);
        return FALSE;
    }

    if(*ndevs > 0){
        *out = HeapAlloc(GetProcessHeap(), 0,
                sizeof(struct DeviceInfo) * (*ndevs));
        if(!*out){
            IMMDeviceCollection_Release(coll);
            return FALSE;
        }

        for(i = 0; i < *ndevs; ++i){
            IMMDevice *dev;

            hr = IMMDeviceCollection_Item(coll, i, &dev);
            if(FAILED(hr)){
                (*out)[i].id = NULL;
                continue;
            }

            load_device(dev, &(*out)[i]);

            IMMDevice_Release(dev);
        }
    }else
        *out = NULL;

    IMMDeviceCollection_Release(coll);

    return TRUE;
}

static BOOL get_driver_name(IMMDeviceEnumerator *devenum, PROPVARIANT *pv)
{
    IMMDevice *device;
    IPropertyStore *ps;
    HRESULT hr;

189
    hr = IMMDeviceEnumerator_GetDevice(devenum, L"Wine info device", &device);
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
    if(FAILED(hr))
        return FALSE;

    hr = IMMDevice_OpenPropertyStore(device, STGM_READ, &ps);
    if(FAILED(hr)){
        IMMDevice_Release(device);
        return FALSE;
    }

    hr = IPropertyStore_GetValue(ps,
            (const PROPERTYKEY *)&DEVPKEY_Device_Driver, pv);
    IPropertyStore_Release(ps);
    IMMDevice_Release(device);
    if(FAILED(hr))
        return FALSE;

    return TRUE;
}
208

209 210
static void initAudioDlg (HWND hDlg)
{
211 212 213 214
    WCHAR display_str[256], format_str[256], sysdefault_str[256], disabled_str[64];
    IMMDeviceEnumerator *devenum;
    BOOL have_driver = FALSE;
    HRESULT hr;
215
    UINT i;
216 217 218 219
    LVCOLUMNW lvcol;
    WCHAR colW[64], speaker_str[256];
    RECT rect;
    DWORD width;
220

221 222
    WINE_TRACE("\n");

223 224 225 226 227
    LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER, format_str, ARRAY_SIZE(format_str));
    LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DRIVER_NONE, disabled_str,
            ARRAY_SIZE(disabled_str));
    LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SYSDEFAULT, sysdefault_str,
            ARRAY_SIZE(sysdefault_str));
228

229 230 231 232 233 234 235 236 237
    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
            CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);
    if(SUCCEEDED(hr)){
        PROPVARIANT pv;

        load_devices(devenum, eRender, &num_render_devs, &render_devs);
        load_devices(devenum, eCapture, &num_capture_devs, &capture_devs);

        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);
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
        }
        PropVariantClear(&pv);

        IMMDeviceEnumerator_Release(devenum);
    }

    SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
            0, (LPARAM)sysdefault_str);
    SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETCURSEL, 0, 0);
    SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
            0, (LPARAM)sysdefault_str);
    SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, 0, 0);

    SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
            0, (LPARAM)sysdefault_str);
    SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, 0, 0);
    SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
            0, (LPARAM)sysdefault_str);
    SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, 0, 0);

262 263
    i = 0;
    while (speaker_configs[i].text_id != 0) {
264 265
        LoadStringW(GetModuleHandleW(NULL), speaker_configs[i].text_id, speaker_str,
                ARRAY_SIZE(speaker_str));
266 267 268 269 270 271 272

        SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_ADDSTRING,
            0, (LPARAM)speaker_str);

        i++;
    }

273 274 275
    GetClientRect(GetDlgItem(hDlg, IDC_LIST_AUDIO_DEVICES), &rect);
    width = (rect.right - rect.left) * 3 / 5;

276
    LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_DEVICE, colW, ARRAY_SIZE(colW));
277 278 279 280 281 282
    lvcol.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
    lvcol.pszText = colW;
    lvcol.cchTextMax = lstrlenW(colW);
    lvcol.cx = width;
    SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvcol);

283
    LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_SPEAKER_CONFIG, colW, ARRAY_SIZE(colW));
284 285 286 287 288 289 290
    lvcol.pszText = colW;
    lvcol.cchTextMax = lstrlenW(colW);
    lvcol.cx = rect.right - rect.left - width;
    SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvcol);

    EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);

291 292 293
    if(have_driver){
        WCHAR *reg_out_dev, *reg_vout_dev, *reg_in_dev, *reg_vin_dev;

294 295 296 297
        reg_out_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultOutput", NULL);
        reg_vout_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceOutput", NULL);
        reg_in_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultInput", NULL);
        reg_vin_dev = get_reg_key(HKEY_CURRENT_USER, g_drv_keyW, L"DefaultVoiceInput", NULL);
298 299

        for(i = 0; i < num_render_devs; ++i){
300 301
            LVITEMW lvitem;

302 303 304 305
            if(!render_devs[i].id)
                continue;

            SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_ADDSTRING,
306
                    0, (LPARAM)render_devs[i].name.pwszVal);
307 308
            SendDlgItemMessageW(hDlg, IDC_AUDIOOUT_DEVICE, CB_SETITEMDATA,
                    i + 1, (LPARAM)&render_devs[i]);
309

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 313
                SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL, render_devs[i].speaker_config, 0);
            }
314 315

            SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_ADDSTRING,
316
                    0, (LPARAM)render_devs[i].name.pwszVal);
317 318
            SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETITEMDATA,
                    i + 1, (LPARAM)&render_devs[i]);
319
            if(reg_vout_dev && !wcscmp(render_devs[i].id, reg_vout_dev))
320 321
                SendDlgItemMessageW(hDlg, IDC_VOICEOUT_DEVICE, CB_SETCURSEL, i + 1, 0);

322 323 324
            lvitem.mask = LVIF_TEXT | LVIF_PARAM;
            lvitem.iItem = i;
            lvitem.iSubItem = 0;
325
            lvitem.pszText = render_devs[i].name.pwszVal;
326 327 328 329 330 331
            lvitem.cchTextMax = lstrlenW(lvitem.pszText);
            lvitem.lParam = (LPARAM)&render_devs[i];

            SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_INSERTITEMW, 0, (LPARAM)&lvitem);

            LoadStringW(GetModuleHandleW(NULL), speaker_configs[render_devs[i].speaker_config].text_id,
332
                    speaker_str, ARRAY_SIZE(speaker_str));
333 334 335 336 337 338 339 340

            lvitem.mask = LVIF_TEXT;
            lvitem.iItem = i;
            lvitem.iSubItem = 1;
            lvitem.pszText = speaker_str;
            lvitem.cchTextMax = lstrlenW(lvitem.pszText);

            SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);
341 342
        }

343 344 345 346 347
        for(i = 0; i < num_capture_devs; ++i){
            if(!capture_devs[i].id)
                continue;

            SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_ADDSTRING,
348
                    0, (LPARAM)capture_devs[i].name.pwszVal);
349 350
            SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETITEMDATA,
                    i + 1, (LPARAM)&capture_devs[i]);
351
            if(reg_in_dev && !wcscmp(capture_devs[i].id, reg_in_dev))
352 353 354
                SendDlgItemMessageW(hDlg, IDC_AUDIOIN_DEVICE, CB_SETCURSEL, i + 1, 0);

            SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_ADDSTRING,
355
                    0, (LPARAM)capture_devs[i].name.pwszVal);
356 357
            SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETITEMDATA,
                    i + 1, (LPARAM)&capture_devs[i]);
358
            if(reg_vin_dev && !wcscmp(capture_devs[i].id, reg_vin_dev))
359 360 361 362 363 364 365 366
                SendDlgItemMessageW(hDlg, IDC_VOICEIN_DEVICE, CB_SETCURSEL, i + 1, 0);
        }

        HeapFree(GetProcessHeap(), 0, reg_out_dev);
        HeapFree(GetProcessHeap(), 0, reg_vout_dev);
        HeapFree(GetProcessHeap(), 0, reg_in_dev);
        HeapFree(GetProcessHeap(), 0, reg_vin_dev);
    }else
367
        swprintf(display_str, ARRAY_SIZE(display_str), format_str, disabled_str);
368 369

    SetDlgItemTextW(hDlg, IDC_AUDIO_DRIVER, display_str);
370 371
}

372 373 374 375 376 377 378 379 380 381 382
static void set_reg_device(HWND hDlg, int dlgitem, const WCHAR *key_name)
{
    UINT idx;
    struct DeviceInfo *info;

    idx = SendDlgItemMessageW(hDlg, dlgitem, CB_GETCURSEL, 0, 0);

    info = (struct DeviceInfo *)SendDlgItemMessageW(hDlg, dlgitem,
            CB_GETITEMDATA, idx, 0);

    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);
386 387
}

388 389
static void test_sound(void)
{
390
    if(!PlaySoundW(MAKEINTRESOURCEW(IDW_TESTSOUND), NULL, SND_RESOURCE | SND_ASYNC)){
391 392
        WCHAR error_str[256], title_str[256];

393 394 395 396
        LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED, error_str,
                ARRAY_SIZE(error_str));
        LoadStringW(GetModuleHandleW(NULL), IDS_AUDIO_TEST_FAILED_TITLE, title_str,
                ARRAY_SIZE(title_str));
397 398 399 400 401

        MessageBoxW(NULL, error_str, title_str, MB_OK | MB_ICONERROR);
    }
}

402 403 404 405 406 407 408 409 410 411 412 413 414
static void apply_speaker_configs(void)
{
    UINT i;
    IMMDeviceEnumerator *devenum;
    IMMDevice *dev;
    IPropertyStore *ps;
    PROPVARIANT pv;
    HRESULT hr;

    hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL,
        CLSCTX_INPROC_SERVER, &IID_IMMDeviceEnumerator, (void**)&devenum);

    if(FAILED(hr)){
415
        ERR("Unable to create MMDeviceEnumerator: 0x%08lx\n", hr);
416 417 418 419 420 421 422 423 424 425
        return;
    }

    PropVariantInit(&pv);
    pv.vt = VT_UI4;

    for (i = 0; i < num_render_devs; i++) {
        hr = IMMDeviceEnumerator_GetDevice(devenum, render_devs[i].id, &dev);

        if(FAILED(hr)){
426
            WARN("Could not get MMDevice for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
427 428 429 430 431 432
            continue;
        }

        hr = IMMDevice_OpenPropertyStore(dev, STGM_WRITE, &ps);

        if(FAILED(hr)){
433
            WARN("Could not open property store for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
434 435 436 437
            IMMDevice_Release(dev);
            continue;
        }

438
        pv.ulVal = speaker_configs[render_devs[i].speaker_config].speaker_mask;
439 440 441 442

        hr = IPropertyStore_SetValue(ps, &PKEY_AudioEndpoint_PhysicalSpeakers, &pv);

        if (FAILED(hr))
443
            WARN("IPropertyStore_SetValue failed for %s: 0x%08lx\n", wine_dbgstr_w(render_devs[i].id), hr);
444 445 446 447 448 449 450 451

        IPropertyStore_Release(ps);
        IMMDevice_Release(dev);
    }

    IMMDeviceEnumerator_Release(devenum);
}

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
static void listview_changed(HWND hDlg)
{
    int idx;

    idx = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
    if(idx < 0) {
        EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 0);
        return;
    }

    SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_SETCURSEL,
            render_devs[idx].speaker_config, 0);

    EnableWindow(GetDlgItem(hDlg, IDC_SPEAKERCONFIG_SPEAKERS), 1);
}

468 469 470 471 472
INT_PTR CALLBACK
AudioDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg) {
      case WM_COMMAND:
473
        switch (LOWORD(wParam)) {
474
          case IDC_AUDIO_TEST:
475
              test_sound();
476
              break;
477 478
          case IDC_AUDIOOUT_DEVICE:
              if(HIWORD(wParam) == CBN_SELCHANGE){
479
                  set_reg_device(hDlg, IDC_AUDIOOUT_DEVICE, L"DefaultOutput");
480 481 482 483 484
                  SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
              }
              break;
          case IDC_VOICEOUT_DEVICE:
              if(HIWORD(wParam) == CBN_SELCHANGE){
485
                  set_reg_device(hDlg, IDC_VOICEOUT_DEVICE, L"DefaultVoiceOutput");
486 487 488 489 490
                  SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
              }
              break;
          case IDC_AUDIOIN_DEVICE:
              if(HIWORD(wParam) == CBN_SELCHANGE){
491
                  set_reg_device(hDlg, IDC_AUDIOIN_DEVICE, L"DefaultInput");
492 493 494 495 496
                  SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
              }
              break;
          case IDC_VOICEIN_DEVICE:
              if(HIWORD(wParam) == CBN_SELCHANGE){
497
                  set_reg_device(hDlg, IDC_VOICEIN_DEVICE, L"DefaultVoiceInput");
498 499 500
                  SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
              }
              break;
501 502 503 504 505
          case IDC_SPEAKERCONFIG_SPEAKERS:
              if(HIWORD(wParam) == CBN_SELCHANGE){
                  UINT dev, idx;

                  idx = SendDlgItemMessageW(hDlg, IDC_SPEAKERCONFIG_SPEAKERS, CB_GETCURSEL, 0, 0);
506
                  dev = SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_GETNEXTITEM, -1, LVNI_SELECTED);
507 508

                  if(dev < num_render_devs){
509 510 511
                      WCHAR speaker_str[256];
                      LVITEMW lvitem;

512
                      render_devs[dev].speaker_config = idx;
513 514

                      LoadStringW(GetModuleHandleW(NULL), speaker_configs[idx].text_id,
515
                          speaker_str, ARRAY_SIZE(speaker_str));
516 517 518 519 520 521 522 523 524

                      lvitem.mask = LVIF_TEXT;
                      lvitem.iItem = dev;
                      lvitem.iSubItem = 1;
                      lvitem.pszText = speaker_str;
                      lvitem.cchTextMax = lstrlenW(lvitem.pszText);

                      SendDlgItemMessageW(hDlg, IDC_LIST_AUDIO_DEVICES, LVM_SETITEMW, 0, (LPARAM)&lvitem);

525 526 527 528
                      SendMessageW(GetParent(hDlg), PSM_CHANGED, 0, 0);
                  }
              }
              break;
529
        }
530 531
        break;

532 533 534
      case WM_SHOWWINDOW:
        set_window_title(hDlg);
        break;
535

536
      case WM_NOTIFY:
537 538
        switch(((LPNMHDR)lParam)->code) {
            case PSN_KILLACTIVE:
539
              SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, FALSE);
540 541
              break;
            case PSN_APPLY:
542
              apply_speaker_configs();
543
              apply();
544
              SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, PSNRET_NOERROR);
545
              break;
546 547
            case PSN_SETACTIVE:
              break;
548 549 550
            case LVN_ITEMCHANGED:
              listview_changed(hDlg);
              break;
551 552 553 554 555
        }
        break;
      case WM_INITDIALOG:
        initAudioDlg(hDlg);
        break;
556 557 558 559
  }

  return FALSE;
}