diff --git a/rostests/winetests/winmm/CMakeLists.txt b/rostests/winetests/winmm/CMakeLists.txt index f762ea8261c..ddaeff7b05f 100644 --- a/rostests/winetests/winmm/CMakeLists.txt +++ b/rostests/winetests/winmm/CMakeLists.txt @@ -1,7 +1,10 @@ list(APPEND SOURCE capture.c + joystick.c mci.c + mcicda.c + midi.c mixer.c mmio.c timer.c @@ -11,7 +14,7 @@ list(APPEND SOURCE add_executable(winmm_winetest ${SOURCE}) target_link_libraries(winmm_winetest dxguid) set_module_type(winmm_winetest win32cui) -add_importlibs(winmm_winetest winmm user32 msvcrt kernel32) +add_importlibs(winmm_winetest winmm user32 advapi32 msvcrt kernel32) add_cd_file(TARGET winmm_winetest DESTINATION reactos/bin FOR all) if(NOT MSVC) diff --git a/rostests/winetests/winmm/capture.c b/rostests/winetests/winmm/capture.c index c46d35a64f4..54c379317cd 100644 --- a/rostests/winetests/winmm/capture.c +++ b/rostests/winetests/winmm/capture.c @@ -27,6 +27,7 @@ #include "windef.h" #include "winbase.h" #include "winnls.h" +#include "mmddk.h" #include "mmsystem.h" #define NOBITMAP #include "mmreg.h" @@ -42,9 +43,9 @@ static const char * wave_in_error(MMRESULT error) static char long_msg[1100]; MMRESULT rc; - rc = waveInGetErrorText(error, msg, sizeof(msg)); + rc = waveInGetErrorTextA(error, msg, sizeof(msg)); if (rc != MMSYSERR_NOERROR) - sprintf(long_msg, "waveInGetErrorText(%x) failed with error %x", error, rc); + sprintf(long_msg, "waveInGetErrorTextA(%x) failed with error %x", error, rc); else sprintf(long_msg, "%s(%s)", mmsys_error(error), msg); return long_msg; @@ -54,14 +55,9 @@ static void check_position(int device, HWAVEIN win, DWORD bytes, LPWAVEFORMATEX pwfx ) { MMTIME mmtime; - DWORD samples; - double duration; MMRESULT rc; DWORD returned; - samples=bytes/(pwfx->wBitsPerSample/8*pwfx->nChannels); - duration=((double)samples)/pwfx->nSamplesPerSec; - mmtime.wType = TIME_BYTES; rc=waveInGetPosition(win, &mmtime, sizeof(mmtime)); ok(rc==MMSYSERR_NOERROR, @@ -131,22 +127,19 @@ static void check_position(int device, HWAVEIN win, DWORD bytes, dev_name(device)); } -static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format, DWORD flags, LPWAVEINCAPS pcaps) +static void wave_in_test_deviceIn(int device, WAVEFORMATEX *pwfx, DWORD format, DWORD flags, + WAVEINCAPSA *pcaps) { HWAVEIN win; - HANDLE hevent; + HANDLE hevent = CreateEventW(NULL, FALSE, FALSE, NULL); WAVEHDR frag; MMRESULT rc; DWORD res; + MMTIME mmt; WORD nChannels = pwfx->nChannels; WORD wBitsPerSample = pwfx->wBitsPerSample; DWORD nSamplesPerSec = pwfx->nSamplesPerSec; - hevent=CreateEvent(NULL,FALSE,FALSE,NULL); - ok(hevent!=NULL,"CreateEvent(): error=%d\n",GetLastError()); - if (hevent==NULL) - return; - win=NULL; rc=waveInOpen(&win,device,pwfx,(DWORD_PTR)hevent,0,CALLBACK_EVENT|flags); /* Note: Win9x doesn't know WAVE_FORMAT_DIRECT */ @@ -159,13 +152,13 @@ static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format, (!(flags & WAVE_FORMAT_DIRECT) || (flags & WAVE_MAPPED)) && !(pcaps->dwFormats & format)) || (rc==MMSYSERR_INVALFLAG && (flags & WAVE_FORMAT_DIRECT)), - "waveInOpen(%s): format=%dx%2dx%d flags=%lx(%s) rc=%s\n", + "waveInOpen(%s): format=%dx%2dx%d flags=%x(%s) rc=%s\n", dev_name(device),pwfx->nSamplesPerSec,pwfx->wBitsPerSample, pwfx->nChannels,CALLBACK_EVENT|flags, wave_open_flags(CALLBACK_EVENT|flags),wave_in_error(rc)); if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) && (flags & WAVE_FORMAT_DIRECT) && (pcaps->dwFormats & format)) - trace(" Reason: The device lists this format as supported in it's " + trace(" Reason: The device lists this format as supported in its " "capabilities but opening it failed.\n"); if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) && !(pcaps->dwFormats & format)) @@ -229,6 +222,13 @@ static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format, "frag.dwBytesRecorded=%d, should=%d\n", frag.dwBytesRecorded,pwfx->nAvgBytesPerSec); + mmt.wType = TIME_BYTES; + rc=waveInGetPosition(win, &mmt, sizeof(mmt)); + ok(rc==MMSYSERR_NOERROR,"waveInGetPosition(%s): rc=%s\n", + dev_name(device),wave_in_error(rc)); + ok(mmt.wType == TIME_BYTES, "doesn't support TIME_BYTES: %u\n", mmt.wType); + ok(mmt.u.cb == frag.dwBytesRecorded, "Got wrong position: %u\n", mmt.u.cb); + /* stop playing on error */ if (res!=WAIT_OBJECT_0) { rc=waveInStop(win); @@ -261,7 +261,7 @@ static void wave_in_test_deviceIn(int device, LPWAVEFORMATEX pwfx, DWORD format, rc==MMSYSERR_ALLOCATED || ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) && !(pcaps->dwFormats & format)), - "waveOutOpen(%s) format=%dx%2dx%d flags=%lx(%s) rc=%s\n", + "waveOutOpen(%s) format=%dx%2dx%d flags=%x(%s) rc=%s\n", dev_name(device),pwfx->nSamplesPerSec,pwfx->wBitsPerSample, pwfx->nChannels,CALLBACK_EVENT|flags, wave_open_flags(CALLBACK_EVENT),wave_out_error(rc)); @@ -299,7 +299,7 @@ static void wave_in_test_device(UINT_PTR device) { WAVEINCAPSA capsA; WAVEINCAPSW capsW; - WAVEFORMATEX format,oformat; + WAVEFORMATEX format; WAVEFORMATEXTENSIBLE wfex; HWAVEIN win; MMRESULT rc; @@ -444,29 +444,6 @@ static void wave_in_test_device(UINT_PTR device) VirtualFree(twoPages, 2 * dwPageSize, MEM_RELEASE); } - /* Testing invalid format: 2 MHz sample rate */ - format.wFormatTag=WAVE_FORMAT_PCM; - format.nChannels=2; - format.wBitsPerSample=16; - format.nSamplesPerSec=2000000; - format.nBlockAlign=format.nChannels*format.wBitsPerSample/8; - format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign; - format.cbSize=0; - oformat=format; - rc=waveInOpen(&win,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT); - ok(rc==WAVERR_BADFORMAT || rc==MMSYSERR_INVALFLAG || - rc==MMSYSERR_INVALPARAM, - "waveInOpen(%s): opening the device with 2 MHz sample rate should fail: " - " rc=%s\n",dev_name(device),wave_in_error(rc)); - if (rc==MMSYSERR_NOERROR) { - trace(" got %dx%2dx%d for %dx%2dx%d\n", - format.nSamplesPerSec, format.wBitsPerSample, - format.nChannels, - oformat.nSamplesPerSec, oformat.wBitsPerSample, - oformat.nChannels); - waveInClose(win); - } - /* test non PCM formats */ format.wFormatTag=WAVE_FORMAT_MULAW; format.nChannels=1; @@ -477,7 +454,8 @@ static void wave_in_test_device(UINT_PTR device) format.cbSize=0; rc=waveInOpen(&win,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -495,7 +473,8 @@ static void wave_in_test_device(UINT_PTR device) format.cbSize=0; rc=waveInOpen(&win,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -519,7 +498,8 @@ static void wave_in_test_device(UINT_PTR device) rc=waveInOpen(&win,device,&wfex.Format,0,0, CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -543,7 +523,8 @@ static void wave_in_test_device(UINT_PTR device) rc=waveInOpen(&win,device,&wfex.Format,0,0, CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -567,7 +548,8 @@ static void wave_in_test_device(UINT_PTR device) rc=waveInOpen(&win,device,&wfex.Format,0,0, CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -594,7 +576,8 @@ static void wave_in_test_device(UINT_PTR device) rc=waveInOpen(&win,device,&wfex.Format,0,0, CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -619,7 +602,8 @@ static void wave_in_test_device(UINT_PTR device) rc=waveInOpen(&win,device,&wfex.Format,0,0, CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -643,7 +627,8 @@ static void wave_in_test_device(UINT_PTR device) rc=waveInOpen(&win,device,&wfex.Format,0,0, CALLBACK_NULL|WAVE_FORMAT_DIRECT); ok(rc==MMSYSERR_NOERROR || rc==WAVERR_BADFORMAT || - rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM, + rc==MMSYSERR_INVALFLAG || rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_ALLOCATED, "waveInOpen(%s): returned: %s\n",dev_name(device),wave_in_error(rc)); if (rc==MMSYSERR_NOERROR) { waveInClose(win); @@ -660,25 +645,30 @@ static void wave_in_tests(void) WAVEFORMATEX format; HWAVEIN win; MMRESULT rc; + DWORD preferred, status; UINT ndev,d; ndev=waveInGetNumDevs(); trace("found %d WaveIn devices\n",ndev); + rc = waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&preferred, (DWORD_PTR)&status); + ok((ndev == 0 && (rc == MMSYSERR_NODRIVER || rc == MMSYSERR_BADDEVICEID)) || + rc == MMSYSERR_NOTSUPPORTED || + rc == MMSYSERR_NOERROR, "waveInMessage(DRVM_MAPPER_PREFERRED_GET) failed: %u\n", rc); + + if(rc != MMSYSERR_NOTSUPPORTED) + ok((ndev == 0 && (preferred == -1 || broken(preferred != -1))) || + preferred < ndev, "Got invalid preferred device: 0x%x\n", preferred); + rc=waveInGetDevCapsA(ndev+1,&capsA,sizeof(capsA)); ok(rc==MMSYSERR_BADDEVICEID, "waveInGetDevCapsA(%s): MMSYSERR_BADDEVICEID expected, got %s\n", dev_name(ndev+1),wave_in_error(rc)); rc=waveInGetDevCapsA(WAVE_MAPPER,&capsA,sizeof(capsA)); - if (ndev>0) - ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER, - "waveInGetDevCapsA(%s): MMSYSERR_NOERROR or MMSYSERR_NODRIVER " - "expected, got %s\n",dev_name(WAVE_MAPPER),wave_in_error(rc)); - else - ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER, - "waveInGetDevCapsA(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NODRIVER " - "expected, got %s\n",dev_name(WAVE_MAPPER),wave_in_error(rc)); + ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER || (!ndev && (rc==MMSYSERR_BADDEVICEID)), + "waveInGetDevCapsA(%s): got %s\n",dev_name(WAVE_MAPPER),wave_in_error(rc)); rc=waveInGetDevCapsW(ndev+1,&capsW,sizeof(capsW)); ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NOTSUPPORTED, @@ -686,18 +676,9 @@ static void wave_in_tests(void) "expected, got %s\n",dev_name(ndev+1),wave_in_error(rc)); rc=waveInGetDevCapsW(WAVE_MAPPER,&capsW,sizeof(capsW)); - if (ndev>0) - ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER || - rc==MMSYSERR_NOTSUPPORTED, - "waveInGetDevCapsW(%s): MMSYSERR_NOERROR or MMSYSERR_NODRIVER or " - "MMSYSERR_NOTSUPPORTED expected, got %s\n", - dev_name(ndev+1),wave_in_error(rc)); - else - ok(rc==MMSYSERR_BADDEVICEID || rc==MMSYSERR_NODRIVER || - rc==MMSYSERR_NOTSUPPORTED, - "waveInGetDevCapsW(%s): MMSYSERR_BADDEVICEID or MMSYSERR_NODRIVER or" - "MMSYSERR_NOTSUPPORTED expected, got %s\n", - dev_name(ndev+1),wave_in_error(rc)); + ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NODRIVER || + rc==MMSYSERR_NOTSUPPORTED || (!ndev && (rc==MMSYSERR_BADDEVICEID)), + "waveInGetDevCapsW(%s): got %s\n", dev_name(ndev+1),wave_in_error(rc)); format.wFormatTag=WAVE_FORMAT_PCM; format.nChannels=2; diff --git a/rostests/winetests/winmm/joystick.c b/rostests/winetests/winmm/joystick.c new file mode 100644 index 00000000000..21e90e30afa --- /dev/null +++ b/rostests/winetests/winmm/joystick.c @@ -0,0 +1,205 @@ +/* + * Unit tests for joystick APIs + * + * Copyright 2014 Bruno Jesus + * + * 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 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include "windef.h" +#include "winbase.h" +#include "winuser.h" +#include "mmsystem.h" +#include "wine/test.h" + +static HWND window; + +static LRESULT CALLBACK proc_window(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +static void create_window(void) +{ + const char name[] = "Joystick Test"; + WNDCLASSA wc; + + memset(&wc, 0, sizeof(wc)); + wc.lpfnWndProc = proc_window; + wc.hInstance = 0; + wc.lpszClassName = name; + RegisterClassA(&wc); + window = CreateWindowExA(0, name, name, WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, NULL, NULL); + ok(window != NULL, "Expected CreateWindowEx to work, error %d\n", GetLastError()); +} + +static void destroy_window(void) +{ + DestroyWindow(window); + window = NULL; +} + +static void test_api(void) +{ + MMRESULT ret; + JOYCAPSA jc; + JOYCAPSW jcw; + JOYINFO info; + union _infoex + { + JOYINFOEX ex; + char buffer[sizeof(JOYINFOEX) * 2]; + } infoex; + UINT i, par, devices, joyid, win98 = 0, win8 = 0; + UINT period[] = {0, 1, 9, 10, 100, 1000, 1001, 10000, 65535, 65536, 0xFFFFFFFF}; + UINT threshold_error = 0x600, period_win8_error = 0x7CE; + UINT flags[] = { JOY_RETURNALL, JOY_RETURNBUTTONS, JOY_RETURNCENTERED, JOY_RETURNPOV, + JOY_RETURNPOVCTS, JOY_RETURNR, JOY_RETURNRAWDATA, JOY_RETURNU, + JOY_RETURNV, JOY_RETURNX, JOY_RETURNY, JOY_RETURNZ }; + + devices = joyGetNumDevs(); + joyid = -1; + /* joyGetNumDevs does NOT return the number of joysticks connected, only slots in the OS */ + for (i = 0; i < devices; i++) + { + memset(&jc, 0, sizeof(jc)); + ret = joyGetDevCapsA(JOYSTICKID1 + i, &jc, sizeof(jc)); + if (ret == JOYERR_NOERROR) + { + joyid = JOYSTICKID1 + i; + trace("Joystick[%d] - name: '%s', axes: %d, buttons: %d, period range: %d - %d\n", + JOYSTICKID1 + i, jc.szPname, jc.wNumAxes, jc.wNumButtons, jc.wPeriodMin, jc.wPeriodMax); + ret = joyGetDevCapsW(JOYSTICKID1 + i, &jcw, sizeof(jcw)); + if (ret != MMSYSERR_NOTSUPPORTED) /* Win 98 */ + { + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + ok(jc.wNumAxes == jcw.wNumAxes, "Expected %d == %d\n", jc.wNumAxes, jcw.wNumAxes); + ok(jc.wNumButtons == jcw.wNumButtons, "Expected %d == %d\n", jc.wNumButtons, jcw.wNumButtons); + } + else win98++; + break; + } + else + { + ok(ret == JOYERR_PARMS, "Expected %d, got %d\n", JOYERR_PARMS, ret); + ret = joyGetDevCapsW(JOYSTICKID1 + i, &jcw, sizeof(jcw)); + ok(ret == JOYERR_PARMS || (ret == MMSYSERR_NOTSUPPORTED) /* Win 98 */, + "Expected %d, got %d\n", JOYERR_PARMS, ret); + } + } + /* Test invalid joystick - If no joystick is present the driver is not initialized, + * so a NODRIVER error is returned, if at least one joystick is present the error is + * about invalid parameters. */ + ret = joyGetDevCapsA(joyid + devices, &jc, sizeof(jc)); + ok(ret == MMSYSERR_NODRIVER || ret == JOYERR_PARMS, + "Expected %d or %d, got %d\n", MMSYSERR_NODRIVER, JOYERR_PARMS, ret); + + if (joyid == -1) + { + skip("This test requires a real joystick.\n"); + return; + } + + /* Capture tests */ + ret = joySetCapture(NULL, joyid, 100, FALSE); + ok(ret == JOYERR_PARMS || broken(win98 && ret == MMSYSERR_INVALPARAM) /* Win 98 */, + "Expected %d, got %d\n", JOYERR_PARMS, ret); + ret = joySetCapture(window, joyid, 100, FALSE); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + ret = joySetCapture(window, joyid, 100, FALSE); /* double capture */ + if (ret == JOYERR_NOCANDO) + { + todo_wine + ok(broken(1), "Expected double capture using joySetCapture to work\n"); + if (!win98 && broken(1)) win8++; /* Windows 98 or 8 cannot cope with that */ + } + else ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + ret = joyReleaseCapture(joyid); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + ret = joyReleaseCapture(joyid); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); /* double release */ + + /* Try some unusual period values for joySetCapture and unusual threshold values for joySetThreshold. + * Windows XP allows almost all test values, Windows 8 will return error on most test values, Windows + * 98 allows anything but cuts the values to their maximum supported values internally. */ + for (i = 0; i < sizeof(period) / sizeof(period[0]); i++) + { + ret = joySetCapture(window, joyid, period[i], FALSE); + if (win8 && ((1 << i) & period_win8_error)) + ok(ret == JOYERR_NOCANDO, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOCANDO, ret); + else + ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret); + ret = joyReleaseCapture(joyid); + ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret); + /* Reuse the periods to test the threshold */ + ret = joySetThreshold(joyid, period[i]); + if (!win98 && (1 << i) & threshold_error) + ok(ret == MMSYSERR_INVALPARAM, "Test [%d]: Expected %d, got %d\n", i, MMSYSERR_INVALPARAM, ret); + else + ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret); + par = 0xdead; + ret = joyGetThreshold(joyid, &par); + ok(ret == JOYERR_NOERROR, "Test [%d]: Expected %d, got %d\n", i, JOYERR_NOERROR, ret); + if (!win98 || (win98 && i < 8)) + { + if ((1 << i) & threshold_error) + ok(par == period[8], "Test [%d]: Expected %d, got %d\n", i, period[8], par); + else + ok(par == period[i], "Test [%d]: Expected %d, got %d\n", i, period[i], par); + } + } + + /* Position tests */ + ret = joyGetPos(joyid, NULL); + ok(ret == MMSYSERR_INVALPARAM, "Expected %d, got %d\n", MMSYSERR_INVALPARAM, ret); + ret = joyGetPos(joyid, &info); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + ret = joyGetPosEx(joyid, NULL); + ok(ret == MMSYSERR_INVALPARAM || broken(win8 && ret == JOYERR_PARMS) /* Win 8 */, + "Expected %d, got %d\n", MMSYSERR_INVALPARAM, ret); + memset(&infoex, 0, sizeof(infoex)); + ret = joyGetPosEx(joyid, &infoex.ex); + ok(ret == JOYERR_PARMS || broken(win98 && ret == MMSYSERR_INVALPARAM), + "Expected %d, got %d\n", JOYERR_PARMS, ret); + infoex.ex.dwSize = sizeof(infoex.ex); + ret = joyGetPosEx(joyid, &infoex.ex); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + infoex.ex.dwSize = sizeof(infoex.ex) - 1; + ret = joyGetPosEx(joyid, &infoex.ex); + ok(ret == JOYERR_PARMS || broken(win98 && ret == MMSYSERR_INVALPARAM), + "Expected %d, got %d\n", JOYERR_PARMS, ret); + infoex.ex.dwSize = sizeof(infoex); + ret = joyGetPosEx(joyid, &infoex.ex); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + + infoex.ex.dwSize = sizeof(infoex.ex); + for (i = 0; i < sizeof(flags) / sizeof(flags[0]); i++) + { + infoex.ex.dwFlags = flags[i]; + ret = joyGetPosEx(joyid, &infoex.ex); + ok(ret == JOYERR_NOERROR, "Expected %d, got %d\n", JOYERR_NOERROR, ret); + } +} + +START_TEST(joystick) +{ + create_window(); + test_api(); + destroy_window(); +} diff --git a/rostests/winetests/winmm/mci.c b/rostests/winetests/winmm/mci.c index 6607e17c99f..a729b5f37cf 100644 --- a/rostests/winetests/winmm/mci.c +++ b/rostests/winetests/winmm/mci.c @@ -30,13 +30,17 @@ static MCIERROR ok_saved = MCIERR_FILE_NOT_FOUND; typedef union { + MCI_INFO_PARMSA info; MCI_STATUS_PARMS status; MCI_WAVE_SET_PARMS set; - MCI_WAVE_OPEN_PARMS open; + MCI_WAVE_OPEN_PARMSA open; + MCI_GETDEVCAPS_PARMS caps; + MCI_SYSINFO_PARMSA sys; MCI_SEEK_PARMS seek; + MCI_GENERIC_PARMS gen; } MCI_PARMS_UNION; -static const char* dbg_mcierr(MCIERROR err) +const char* dbg_mcierr(MCIERROR err) { switch (err) { case 0: return "0=NOERROR"; @@ -135,65 +139,289 @@ static BOOL spurious_message(LPMSG msg) return FALSE; } -static void test_notification(HWND hwnd, const char* command, WPARAM type) +/* A single ok() in each code path allows us to prefix this with todo_wine */ +#define test_notification(hwnd, command, type) test_notification_dbg(hwnd, command, type, __LINE__) +static void test_notification_dbg(HWND hwnd, const char* command, WPARAM type, int line) { /* Use type 0 as meaning no message */ MSG msg; BOOL seen; do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); } while(seen && spurious_message(&msg)); - if(type==0) - ok(!seen, "Expect no message from command %s\n", command); - else - ok(seen, "PeekMessage should succeed for command %s\n", command); - if(seen) { - ok(msg.hwnd == hwnd, "Didn't get the handle to our test window\n"); - ok(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command); - ok(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command); + if(type && !seen) { + /* We observe transient delayed notification, mostly on native. + * Notification is not always present right when mciSend returns. */ + trace_(__FILE__,line)("Waiting for delayed notification from %s\n", command); + MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE); + seen = PeekMessageA(&msg, hwnd, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE); } + if(!seen) + ok_(__FILE__,line)(type==0, "Expect message %04lx from %s\n", type, command); + else if(msg.hwnd != hwnd) + ok_(__FILE__,line)(msg.hwnd == hwnd, "Didn't get the handle to our test window\n"); + else if(msg.message != MM_MCINOTIFY) + ok_(__FILE__,line)(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command); + else ok_(__FILE__,line)(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command); } -static void test_notification1(HWND hwnd, const char* command, WPARAM type) -{ /* This version works with todo_wine prefix. */ - MSG msg; - BOOL seen; - do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); } - while(seen && spurious_message(&msg)); - if(type==0) - ok(!seen, "Expect no message from command %s\n", command); - else if(seen) - ok(msg.message == MM_MCINOTIFY && msg.wParam == type,"got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command); - else ok(seen, "PeekMessage should succeed for command %s\n", command); + +static int strcmp_wa(LPCWSTR strw, const char *stra) +{ + CHAR buf[512]; + WideCharToMultiByte(CP_ACP, 0, strw, -1, buf, sizeof(buf), 0, 0); + return lstrcmpA(buf, stra); +} + +static void test_mciParser(HWND hwnd) +{ + MCIERROR err; + MCIDEVICEID wDeviceID; + MCI_PARMS_UNION parm; + char buf[1024]; + memset(buf, 0, sizeof(buf)); + test_notification(hwnd, "-prior to parser test-", 0); + + /* Get a handle on an MCI device, works even without sound. */ + parm.open.lpstrDeviceType = "waveaudio"; + parm.open.lpstrElementName = ""; /* "new" at the command level */ + parm.open.lpstrAlias = "x"; /* to enable mciSendStringA */ + parm.open.dwCallback = (DWORD_PTR)hwnd; + err = mciSendCommandA(0, MCI_OPEN, + MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_ALIAS | MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err,"mciCommand open new type waveaudio alias x notify: %s\n", dbg_mcierr(err)); + wDeviceID = parm.open.wDeviceID; + ok(!strcmp(parm.open.lpstrDeviceType,"waveaudio"), "open modified device type\n"); + + test_notification(hwnd, "MCI_OPEN", MCI_NOTIFY_SUCCESSFUL); + test_notification(hwnd, "MCI_OPEN no #2", 0); + + err = mciSendStringA("open avivideo alias a", buf, sizeof(buf), hwnd); + ok(!err,"open another: %s\n", dbg_mcierr(err)); + + buf[0]='z'; + err = mciSendStringA("", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_MISSING_COMMAND_STRING,"empty string: %s\n", dbg_mcierr(err)); + ok(!buf[0], "error buffer %s\n", buf); + + buf[0]='d'; + err = mciSendStringA("open", buf, sizeof(buf), NULL); + ok(err==MCIERR_MISSING_DEVICE_NAME,"open void: %s\n", dbg_mcierr(err)); + ok(!buf[0], "open error buffer %s\n", buf); + + err = mciSendStringA("open notify", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_INVALID_DEVICE_NAME,"open notify: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("open new", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_NEW_REQUIRES_ALIAS,"open new: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("open new type waveaudio alias r shareable shareable", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_DUPLICATE_FLAGS,"open new: %s\n", dbg_mcierr(err)); + if(!err) mciSendStringA("close r", NULL, 0, NULL); + + err = mciSendStringA("status x position wait wait", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_DUPLICATE_FLAGS,"status wait wait: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x length length", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"status 2xlength: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x length position", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"status length+position: %s\n", dbg_mcierr(err)); + + buf[0]='I'; + err = mciSendStringA("set x time format milliseconds time format ms", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"status length+position: %s\n", dbg_mcierr(err)); + ok(!buf[0], "set error buffer %s\n", buf); + + /* device's response, not a parser test */ + err = mciSendStringA("status x", buf, sizeof(buf), NULL); + ok(err==MCIERR_MISSING_PARAMETER,"status waveaudio nokeyword: %s\n", dbg_mcierr(err)); + + buf[0]='G'; + err = mciSendStringA("status a", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_UNSUPPORTED_FUNCTION,"status avivideo nokeyword: %s\n", dbg_mcierr(err)); + ok(!buf[0], "status error buffer %s\n", buf); + + err = mciSendStringA("status x track", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"status waveaudio no track: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x track 3", buf, sizeof(buf), NULL); + ok(err==MCIERR_MISSING_PARAMETER,"status waveaudio track 3: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x 2 track 3", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_OUTOFRANGE,"status 2(position) track 3: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x 0x4", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_BAD_CONSTANT, "status 0x4: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x 4", buf, sizeof(buf), hwnd); + ok(!err,"status 4(mode): %s\n", dbg_mcierr(err)); + if(!err)ok(!strcmp(buf,"stopped"), "status 4(mode), got: %s\n", buf); + + err = mciSendStringA("status x 4 notify", buf, sizeof(buf), hwnd); + todo_wine ok(!err,"status 4(mode) notify: %s\n", dbg_mcierr(err)); + if(!err)ok(!strcmp(buf,"stopped"), "status 4(mode), got: %s\n", buf); + test_notification(hwnd, "status 4 notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + + err = mciSendStringA("set x milliseconds", buf, sizeof(buf), hwnd); + todo_wine ok(err==MCIERR_UNRECOGNIZED_KEYWORD,"set milliseconds: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("set x milliseconds ms", buf, sizeof(buf), hwnd); + todo_wine ok(err==MCIERR_UNRECOGNIZED_KEYWORD,"set milliseconds ms: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("capability x can save", buf, sizeof(buf), hwnd); + todo_wine ok(!err,"capability can (space) save: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status x nsa", buf, sizeof(buf), hwnd); + todo_wine ok(err==MCIERR_BAD_CONSTANT,"status nsa: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek x to 0:0:0:0:0", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"seek to 0:0:0:0:0 returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek x to 0:0:0:0:", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"seek to 0:0:0:0: returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek x to :0:0:0:0", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"seek to :0:0:0:0 returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek x to 256:0:0:0", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"seek to 256:0:0:0 returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek x to 0:256", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"seek to 0:256 returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status all time format", buf, sizeof(buf), hwnd); + ok(err==MCIERR_CANNOT_USE_ALL,"status all: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("cue all", buf, sizeof(buf), NULL); + ok(err==MCIERR_UNRECOGNIZED_COMMAND,"cue all: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("open all", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_CANNOT_USE_ALL,"open all: %s\n", dbg_mcierr(err)); + + /* avivideo is not a known MCI_DEVTYPE resource name */ + err = mciSendStringA("sysinfo avivideo quantity", buf, sizeof(buf), hwnd); + ok(err==MCIERR_DEVICE_TYPE_REQUIRED,"sysinfo sequencer quantity: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("sysinfo digitalvideo quantity", buf, sizeof(buf), hwnd); + ok(!err,"sysinfo digitalvideo quantity: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf,"0"), "sysinfo digitalvideo quantity returned %s\n", buf); + + /* quantity 0 yet open 1 (via type "avivideo"), fun */ + err = mciSendStringA("sysinfo digitalvideo quantity open", buf, sizeof(buf), hwnd); + ok(!err,"sysinfo digitalvideo quantity open: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf,"1"), "sysinfo digitalvideo quantity open returned %s\n", buf); + + err = mciSendStringA("put a window at 0 0", buf, sizeof(buf), NULL); + ok(err==MCIERR_BAD_INTEGER,"put incomplete rect: %s\n", dbg_mcierr(err)); + + /*w9X-w2k report code from device last opened, newer versions compare them all + * and return the one error code or MCIERR_MULTIPLE if they differ. */ + err = mciSendStringA("pause all", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_MULTIPLE || broken(err==MCIERR_NONAPPLICABLE_FUNCTION),"pause all: %s\n", dbg_mcierr(err)); + ok(!buf[0], "pause error buffer %s\n", buf); + + /* MCI_STATUS' dwReturn is a DWORD_PTR, others' a plain DWORD. */ + parm.status.dwItem = MCI_STATUS_TIME_FORMAT; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(!err,"mciCommand status time format: %s\n", dbg_mcierr(err)); + if(!err) ok(MCI_FORMAT_MILLISECONDS==parm.status.dwReturn,"status time format: %ld\n",parm.status.dwReturn); + + parm.status.dwItem = MCI_STATUS_MODE; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(!err,"mciCommand status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(MCI_MODE_STOP==parm.status.dwReturn,"STATUS mode: %ld\n",parm.status.dwReturn); + + err = mciSendStringA("status x mode", buf, sizeof(buf), hwnd); + ok(!err,"status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped"), "status mode is %s\n", buf); + + parm.caps.dwItem = MCI_GETDEVCAPS_USES_FILES; + parm.caps.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm); + ok(!err,"mciCommand getdevcaps files: %s\n", dbg_mcierr(err)); + if(!err) ok(1==parm.caps.dwReturn,"getdevcaps files: %d\n",parm.caps.dwReturn); + + parm.caps.dwItem = MCI_GETDEVCAPS_HAS_VIDEO; + parm.caps.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm); + ok(!err,"mciCommand getdevcaps video: %s\n", dbg_mcierr(err)); + if(!err) ok(0==parm.caps.dwReturn,"getdevcaps video: %d\n",parm.caps.dwReturn); + + parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE; + parm.caps.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm); + ok(!err,"mciCommand getdevcaps video: %s\n", dbg_mcierr(err)); + if(!err) ok(MCI_DEVTYPE_WAVEFORM_AUDIO==parm.caps.dwReturn,"getdevcaps device type: %d\n",parm.caps.dwReturn); + + err = mciSendStringA("capability x uses files", buf, sizeof(buf), hwnd); + ok(!err,"capability files: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "true"), "capability files is %s\n", buf); + + err = mciSendStringA("capability x has video", buf, sizeof(buf), hwnd); + ok(!err,"capability video: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf); + + err = mciSendStringA("capability x device type", buf, sizeof(buf), hwnd); + ok(!err,"capability device type: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "waveaudio"), "capability device type is %s\n", buf); + + err = mciSendCommandA(wDeviceID, MCI_CLOSE, 0, 0); + ok(!err,"mciCommand close returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("close a", buf, sizeof(buf), hwnd); + ok(!err,"close avi: %s\n", dbg_mcierr(err)); + + test_notification(hwnd, "-end of 1st set-", 0); } static void test_openCloseWAVE(HWND hwnd) { MCIERROR err; - MCI_GENERIC_PARMS parm; + MCI_PARMS_UNION parm; const char command_open[] = "open new type waveaudio alias mysound notify"; const char command_close_my[] = "close mysound notify"; const char command_close_all[] = "close all notify"; const char command_sysinfo[] = "sysinfo waveaudio quantity open"; char buf[1024]; + DWORD intbuf[3] = { 0xDEADF00D, 99, 0xABADCAFE }; memset(buf, 0, sizeof(buf)); test_notification(hwnd, "-prior to any command-", 0); - err = mciSendString("sysinfo all quantity", buf, sizeof(buf), hwnd); - todo_wine ok(!err,"mci %s returned %s\n", command_open, dbg_mcierr(err)); + /* Avoid Sysinfo quantity with notify because Win9x and newer differ. */ + err = mciSendStringA("sysinfo all quantity", buf, sizeof(buf), hwnd); + ok(!err,"mci sysinfo all quantity returned %s\n", dbg_mcierr(err)); if(!err) trace("[MCI] with %s drivers\n", buf); - err = mciSendString("open new type waveaudio alias r shareable", buf, sizeof(buf), NULL); + parm.sys.lpstrReturn = (LPSTR)&intbuf[1]; + parm.sys.dwRetSize = sizeof(DWORD); + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */ + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_QUANTITY | MCI_WAIT, + (DWORD_PTR)&parm); + ok(!err, "mciCommand sysinfo all quantity returned %s\n", dbg_mcierr(err)); + if(!err) ok(atoi(buf)==intbuf[1],"sysinfo all quantity string and command differ\n"); + + parm.sys.dwRetSize = sizeof(DWORD)-1; + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_QUANTITY, (DWORD_PTR)&parm); + ok(err == MCIERR_PARAM_OVERFLOW || broken(!err/* Win9x */), + "mciCommand sysinfo with too small buffer returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("open new type waveaudio alias r shareable", buf, sizeof(buf), NULL); ok(err==MCIERR_UNSUPPORTED_FUNCTION,"mci open new shareable returned %s\n", dbg_mcierr(err)); if(!err) { - err = mciSendString("close r", NULL, 0, NULL); + err = mciSendStringA("close r", NULL, 0, NULL); ok(!err,"mci close shareable returned %s\n", dbg_mcierr(err)); } - err = mciSendString(command_open, buf, sizeof(buf), hwnd); + err = mciGetDeviceIDA("waveaudio"); + ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err); + + err = mciSendStringA(command_open, buf, sizeof(buf), hwnd); ok(!err,"mci %s returned %s\n", command_open, dbg_mcierr(err)); ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf); /* Wine<=1.1.33 used to ignore anything past alias XY */ test_notification(hwnd,"open new alias notify",MCI_NOTIFY_SUCCESSFUL); - err = mciSendString("status mysound time format", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound time format", buf, sizeof(buf), hwnd); ok(!err,"mci status time format returned %s\n", dbg_mcierr(err)); if(!err) { if (PRIMARYLANGID(LANGIDFROMLCID(GetThreadLocale())) == LANG_ENGLISH) @@ -201,54 +429,190 @@ static void test_openCloseWAVE(HWND hwnd) else trace("locale-dependent time format: %s (ms)\n", buf); } - err = mciSendString(command_close_my, NULL, 0, hwnd); + memset(buf, 0, sizeof(buf)); + parm.sys.dwNumber = 1; + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */ + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = sizeof(buf); + parm.sys.dwCallback = (DWORD_PTR)hwnd; + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, + MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN | MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err,"mciCommand MCI_SYSINFO all name 1 open notify: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf,"mysound"), "sysinfo name returned %s\n", buf); + test_notification(hwnd, "SYSINFO name notify\n", MCI_NOTIFY_SUCCESSFUL); + + memset(buf, 0, sizeof(buf)); + parm.sys.dwNumber = 1; + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */ + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = 8; /* mysound\0 */ + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN, + (DWORD_PTR)&parm); + ok(!err,"mciCommand MCI_SYSINFO all name 1 open buffer[8]: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf,"mysound"), "sysinfo name returned %s\n", buf); + + memset(buf, 0, sizeof(buf)); + /* dwRetSize counts characters, not bytes, despite what MSDN says. */ + parm.sys.dwNumber = 1; + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */ + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = 8; /* mysound\0 */ + /* MCI_..._PARMSA and PARMSW share the same layout, use one for both tests. */ + err = mciSendCommandW(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN, (DWORD_PTR)&parm); + ok(!err || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommandW MCI_SYSINFO all name 1 open buffer[8]: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp_wa((LPWSTR)buf,"mysound"), "sysinfo name 1 open contents\n"); + + memset(buf, 0, sizeof(buf)); + buf[0] = 'Y'; + parm.sys.dwNumber = 1; + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */ + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = 7; /* too short for mysound\0 */ + err = mciSendCommandW(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN, (DWORD_PTR)&parm); + ok(err==MCIERR_PARAM_OVERFLOW || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommandW MCI_SYSINFO all name 1 open too small: %s\n", dbg_mcierr(err)); + ok(!strcmp(buf,"Y"), "output buffer %s\n", buf); + + /* Win9x overwrites the tiny buffer and returns success, newer versions signal overflow. */ + memset(buf, 0, sizeof(buf)); + buf[0] = 'Y'; + parm.sys.dwNumber = 1; + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; /* ignored */ + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = 2; /* too short for mysound\0 */ + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_NAME | MCI_SYSINFO_OPEN, + (DWORD_PTR)&parm); + ok(err==MCIERR_PARAM_OVERFLOW || broken(!err /* Win9x */),"mciCommand MCI_SYSINFO all name 1 open too small: %s\n", dbg_mcierr(err)); + ok(!strcmp(buf, err ? "Y" : "mysound"), "sysinfo short name returned %s\n", buf); + + err = mciSendStringA("sysinfo mysound quantity open", buf, sizeof(buf), hwnd); + ok(err==MCIERR_DEVICE_TYPE_REQUIRED,"sysinfo alias quantity: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("sysinfo nosuchalias quantity open", buf, sizeof(buf), hwnd); + ok(err==MCIERR_DEVICE_TYPE_REQUIRED,"sysinfo unknown quantity open: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("sysinfo all installname", buf, sizeof(buf), hwnd); + ok(err==MCIERR_CANNOT_USE_ALL,"sysinfo all installname: %s\n", dbg_mcierr(err)); + + buf[0] = 'M'; buf[1] = 0; + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = sizeof(buf); + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME, (DWORD_PTR)&parm); + ok(err==MCIERR_CANNOT_USE_ALL,"mciCommand MCI_SYSINFO all installname: %s\n", dbg_mcierr(err)); + ok(!strcmp(buf,"M"), "output buffer %s\n", buf); + + err = mciSendStringA("sysinfo nodev installname", buf, sizeof(buf), hwnd); + ok(err==MCIERR_INVALID_DEVICE_NAME,"sysinfo nodev installname: %s\n", dbg_mcierr(err)); + ok(!buf[0], "sysinfo error buffer %s\n", buf); + + buf[0] = 'K'; + parm.sys.lpstrReturn = buf; + parm.sys.dwRetSize = sizeof(buf); + err = mciSendCommandW(24000, MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME, (DWORD_PTR)&parm); + ok(err==MCIERR_INVALID_DEVICE_NAME || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommand MCI_SYSINFO nodev installname: %s\n", dbg_mcierr(err)); + ok(!strcmp(buf,"K"), "output buffer %s\n", buf); + + buf[0] = 0; buf[1] = 'A'; buf[2] = 'j'; buf[3] = 0; + parm.info.lpstrReturn = buf; + parm.info.dwRetSize = 2; + err = mciSendCommandA(1, MCI_INFO, MCI_INFO_PRODUCT, (DWORD_PTR)&parm); + ok(!err, "mciCommand MCI_INFO product: %s\n", dbg_mcierr(err)); + ok(buf[0] /* && !buf[1] */ && (buf[2] == 'j' || broken(!buf[2])), "info product output buffer %s\n", buf); + /* Producing non-ASCII multi-byte output, native forgets to zero-terminate a too small buffer + * with SendStringA, while SendStringW works correctly (jap. and chin. locale): ignore buf[1] */ + /* Bug in 64 bit Vista/w2k8/w7: mciSendStringW is used! (not in xp nor w2k3) */ + + buf[0] = 'K'; buf[1] = 0; + parm.info.dwRetSize = sizeof(buf); + err = mciSendCommandW(1, MCI_INFO, 0x07000000, (DWORD_PTR)&parm); + ok(err==MCIERR_UNRECOGNIZED_KEYWORD || broken(err==MMSYSERR_NOTSUPPORTED/* Win9x */), "mciCommand MCI_INFO other: %s\n", dbg_mcierr(err)); + ok(!strcmp(buf,"K"), "info output buffer %s\n", buf); + + err = mciGetDeviceIDA("all"); + ok(err == MCI_ALL_DEVICE_ID, "mciGetDeviceIDA all returned %u, expected MCI_ALL_DEVICE_ID\n", err); + + err = mciSendStringA(command_close_my, NULL, 0, hwnd); ok(!err,"mci %s returned %s\n", command_close_my, dbg_mcierr(err)); test_notification(hwnd, command_close_my, MCI_NOTIFY_SUCCESSFUL); Sleep(5); test_notification(hwnd, command_close_my, 0); - err = mciSendString("open no-such-file-exists.wav alias y", buf, sizeof(buf), NULL); + err = mciSendStringA("open no-such-file-exists.wav alias y buffer 6", buf, sizeof(buf), NULL); ok(err==MCIERR_FILE_NOT_FOUND,"open no-such-file.wav returned %s\n", dbg_mcierr(err)); if(!err) { - err = mciSendString("close y", NULL, 0, NULL); + err = mciSendStringA("close y", NULL, 0, NULL); ok(!err,"close y returned %s\n", dbg_mcierr(err)); } - err = mciSendString("open no-such-dir\\file.wav alias y type waveaudio", buf, sizeof(buf), NULL); + err = mciSendStringA("open no-such-dir\\file.wav alias y type waveaudio", buf, sizeof(buf), NULL); ok(err==MCIERR_FILE_NOT_FOUND || broken(err==MCIERR_INVALID_FILE /* Win9X */),"open no-such-dir/file.wav returned %s\n", dbg_mcierr(err)); if(!err) { - err = mciSendString("close y", NULL, 0, NULL); + err = mciSendStringA("close y", NULL, 0, NULL); ok(!err,"close y returned %s\n", dbg_mcierr(err)); } - err = mciSendString(command_close_all, NULL, 0, NULL); - todo_wine ok(!err,"mci %s (without buffer) returned %s\n", command_close_all, dbg_mcierr(err)); + err = mciSendStringA("open ! alias no", buf, sizeof(buf), NULL); + ok(err==MCIERR_INVALID_DEVICE_NAME,"open !(void): %s\n", dbg_mcierr(err)); - memset(buf, 0, sizeof(buf)); - err = mciSendString(command_close_all, buf, sizeof(buf), hwnd); - todo_wine ok(!err,"mci %s (with output buffer) returned %s\n", command_close_all, dbg_mcierr(err)); - ok(buf[0] == 0, "mci %s changed output buffer: %s\n", command_close_all, buf); + err = mciSendStringA("open !no-such-file-exists.wav alias no", buf, sizeof(buf), NULL); + ok(err==MCIERR_FILE_NOT_FOUND || /* Win9X */err==MCIERR_INVALID_DEVICE_NAME,"open !name: %s\n", dbg_mcierr(err)); + + /* FILE_NOT_FOUND stems from mciwave, + * the complete name including ! is passed through since NT */ + err = mciSendStringA("open nosuchdevice!tempfile.wav alias no", buf, sizeof(buf), NULL); + ok(err==MCIERR_FILE_NOT_FOUND || /* Win9X */err==MCIERR_INVALID_DEVICE_NAME,"open nosuchdevice!name: %s\n", dbg_mcierr(err)); + /* FIXME? use broken(INVALID_DEVICE_NAME) and have Wine not mimic Win9X? */ + + err = mciSendStringA("close waveaudio", buf, sizeof(buf), NULL); + ok(err==MCIERR_INVALID_DEVICE_NAME,"close waveaudio: %s\n", dbg_mcierr(err)); + + err = mciSendStringA(command_close_all, NULL, 0, NULL); + ok(!err,"mci %s (without buffer) returned %s\n", command_close_all, dbg_mcierr(err)); + + err = mciSendStringA(command_close_all, buf, sizeof(buf), hwnd); + ok(!err,"mci %s (with output buffer) returned %s\n", command_close_all, dbg_mcierr(err)); + ok(buf[0] == 0, "mci %s output buffer: %s\n", command_close_all, buf); /* No notification left, everything closed already */ test_notification(hwnd, command_close_all, 0); /* TODO test close all sends one notification per open device */ - memset(buf, 0, sizeof(buf)); - err = mciSendString(command_sysinfo, buf, sizeof(buf), NULL); + err = mciSendStringA(command_sysinfo, buf, sizeof(buf), NULL); ok(!err,"mci %s returned %s\n", command_sysinfo, dbg_mcierr(err)); - todo_wine ok(buf[0] == '0' && buf[1] == 0, "mci %s, expected output buffer '0', got: '%s'\n", command_sysinfo, buf); + ok(buf[0] == '0' && buf[1] == 0, "mci %s, expected output buffer '0', got: '%s'\n", command_sysinfo, buf); - err = mciSendString("open new type waveaudio", buf, sizeof(buf), NULL); + err = mciSendStringA("open new type waveaudio", buf, sizeof(buf), NULL); ok(err==MCIERR_NEW_REQUIRES_ALIAS,"mci open new without alias returned %s\n", dbg_mcierr(err)); - err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0); /* from MSDN */ - ok(!err,"mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0) returned %s\n", dbg_mcierr(err)); + parm.open.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_WAVEFORM_AUDIO; + err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD_PTR)&parm); + ok(!err,"mciCommand OPEN_TYPE_ID waveaudio: %s\n", dbg_mcierr(err)); - err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0); - ok(!err,"mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0) returned %s\n", dbg_mcierr(err)); + if(!err) { + MCIDEVICEID wDeviceID = parm.open.wDeviceID; + parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm); + ok(!err,"mciCommand MCI_GETDEVCAPS device type: %s\n", dbg_mcierr(err)); + ok(MCI_DEVTYPE_WAVEFORM_AUDIO==parm.caps.dwReturn,"mciCommand GETDEVCAPS says %u, expected %u\n", parm.caps.dwReturn, MCI_DEVTYPE_WAVEFORM_AUDIO); + } - parm.dwCallback = (DWORD_PTR)hwnd; - err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, (DWORD_PTR)&parm); - ok(!err,"mciSendCommand(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, hwnd) returned %s\n", dbg_mcierr(err)); + ok(0xDEADF00D==intbuf[0] && 0xABADCAFE==intbuf[2],"DWORD buffer corruption\n"); + + err = mciGetDeviceIDA("waveaudio"); + ok(err == 1, "mciGetDeviceIDA waveaudio returned %u, expected 1\n", err); + + err = mciSendStringA("open no-such-file.wav alias waveaudio", buf, sizeof(buf), NULL); + ok(err==MCIERR_DUPLICATE_ALIAS, "mci open alias waveaudio returned %s\n", dbg_mcierr(err)); + /* If it were not already in use, open avivideo alias waveaudio would succeed, + * making for funny test cases. */ + + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_WAIT, 0); /* from MSDN */ + ok(!err, "mciCommand close returned %s\n", dbg_mcierr(err)); + + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, 0); + ok(!err, "mciCommand close returned %s\n", dbg_mcierr(err)); + + parm.gen.dwCallback = (DWORD_PTR)hwnd; + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_CLOSE, MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err, "mciCommand close returned %s\n", dbg_mcierr(err)); test_notification(hwnd, command_close_all, 0); /* None left */ } @@ -257,6 +621,7 @@ static void test_recordWAVE(HWND hwnd) WORD nch = 1; WORD nbits = 16; DWORD nsamp = 16000, expect; + UINT ndevs = waveInGetNumDevs(); MCIERROR err, ok_pcm; MCIDEVICEID wDeviceID; MCI_PARMS_UNION parm; @@ -266,134 +631,166 @@ static void test_recordWAVE(HWND hwnd) parm.open.lpstrDeviceType = "waveaudio"; parm.open.lpstrElementName = ""; /* "new" at the command level */ - parm.open.lpstrAlias = "x"; /* to enable mciSendString */ + parm.open.lpstrAlias = "x"; /* to enable mciSendStringA */ parm.open.dwCallback = (DWORD_PTR)hwnd; - err = mciSendCommand(0, MCI_OPEN, - MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_ALIAS | MCI_NOTIFY, - (DWORD_PTR)&parm); + err = mciSendCommandA(0, MCI_OPEN, + MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_ALIAS | MCI_NOTIFY, (DWORD_PTR)&parm); ok(!err,"mciCommand open new type waveaudio alias x notify: %s\n", dbg_mcierr(err)); wDeviceID = parm.open.wDeviceID; - /* In Wine, both MCI_Open and the individual drivers send notifications. */ + err = mciGetDeviceIDA("x"); + ok(err == wDeviceID, "mciGetDeviceIDA x returned %u, expected %u\n", err, wDeviceID); + + /* Only the alias is looked up. */ + err = mciGetDeviceIDA("waveaudio"); + ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err); + test_notification(hwnd, "open new", MCI_NOTIFY_SUCCESSFUL); - todo_wine test_notification1(hwnd, "open new no #2", 0); + test_notification(hwnd, "open new no #2", 0); /* Do not query time format as string because result depends on locale! */ parm.status.dwItem = MCI_STATUS_TIME_FORMAT; - err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); ok(!err,"mciCommand status time format: %s\n", dbg_mcierr(err)); ok(parm.status.dwReturn==MCI_FORMAT_MILLISECONDS,"status time format: %ld\n",parm.status.dwReturn); /* Info file fails until named in Open or Save. */ - err = mciSendString("info x file", buf, sizeof(buf), NULL); + err = mciSendStringA("info x file", buf, sizeof(buf), NULL); todo_wine ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci info new file returned %s\n", dbg_mcierr(err)); + ok(!buf[0], "info error buffer %s\n", buf); + + err = mciSendStringA("status x length", buf, sizeof(buf), NULL); + todo_wine ok(!err,"status x length initial: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf,"0"), "mci status length expected 0, got: %s\n", buf); /* Check the default recording: 8-bits per sample, mono, 11kHz */ - err = mciSendString("status x samplespersec", buf, sizeof(buf), NULL); + err = mciSendStringA("status x samplespersec", buf, sizeof(buf), NULL); ok(!err,"mci status samplespersec returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"11025"), "mci status samplespersec expected 11025, got: %s\n", buf); /* MCI seems to solely support PCM, no need for ACM conversion. */ - err = mciSendString("set x format tag 2", NULL, 0, NULL); + err = mciSendStringA("set x format tag 2", NULL, 0, NULL); ok(err==MCIERR_OUTOFRANGE,"mci set format tag 2 returned %s\n", dbg_mcierr(err)); /* MCI appears to scan the available devices for support of this format, * returning MCIERR_OUTOFRANGE on machines with no sound. + * However some w2k8/w7 machines return no error when there's no wave + * input device (perhaps querying waveOutGetNumDevs instead of waveIn?), + * still the record command below fails with MCIERR_WAVE_INPUTSUNSUITABLE. * Don't skip here, record will fail below. */ - err = mciSendString("set x format tag pcm", NULL, 0, NULL); + err = mciSendStringA("set x format tag pcm", NULL, 0, NULL); ok(!err || err==MCIERR_OUTOFRANGE,"mci set format tag pcm returned %s\n", dbg_mcierr(err)); ok_pcm = err; - err = mciSendString("set x samplespersec 41000 alignment 4 channels 2", NULL, 0, NULL); - ok(err==ok_pcm,"mci set samples+align+channels returned %s\n", dbg_mcierr(err)); - + /* MSDN warns against not setting all wave format parameters. + * Indeed, it produces strange results, incl. + * inconsistent PCMWAVEFORMAT headers in the saved file. + */ + err = mciSendStringA("set x bytespersec 22050 alignment 2 samplespersec 11025 channels 1 bitspersample 16", NULL, 0, NULL); + ok(err==ok_pcm,"mci set 5 wave parameters returned %s\n", dbg_mcierr(err)); /* Investigate: on w2k, set samplespersec 22050 sets nChannels to 2! - * err = mciSendString("set x samplespersec 22050", NULL, 0, NULL); + * err = mciSendStringA("set x samplespersec 22050", NULL, 0, NULL); * ok(!err,"mci set samplespersec returned %s\n", dbg_mcierr(err)); */ + /* Checks are generally performed immediately. */ + err = mciSendStringA("set x bitspersample 4", NULL, 0, NULL); + todo_wine ok(err==MCIERR_OUTOFRANGE,"mci set bitspersample 4 returned %s\n", dbg_mcierr(err)); + parm.set.wFormatTag = WAVE_FORMAT_PCM; parm.set.nSamplesPerSec = nsamp; parm.set.wBitsPerSample = nbits; parm.set.nChannels = nch; parm.set.nBlockAlign = parm.set.nChannels * parm.set.wBitsPerSample /8; parm.set.nAvgBytesPerSec= parm.set.nSamplesPerSec * parm.set.nBlockAlign; - err = mciSendCommand(wDeviceID, MCI_SET, - MCI_WAVE_SET_SAMPLESPERSEC | MCI_WAVE_SET_CHANNELS | - MCI_WAVE_SET_BITSPERSAMPLE | MCI_WAVE_SET_BLOCKALIGN | - MCI_WAVE_SET_AVGBYTESPERSEC| MCI_WAVE_SET_FORMATTAG, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_SET, + MCI_WAVE_SET_SAMPLESPERSEC | MCI_WAVE_SET_CHANNELS | MCI_WAVE_SET_BITSPERSAMPLE | + MCI_WAVE_SET_BLOCKALIGN | MCI_WAVE_SET_AVGBYTESPERSEC| MCI_WAVE_SET_FORMATTAG, + (DWORD_PTR)&parm); ok(err==ok_pcm,"mciCommand set wave format: %s\n", dbg_mcierr(err)); + parm.caps.dwItem = MCI_WAVE_GETDEVCAPS_INPUTS; + parm.caps.dwCallback = (DWORD_PTR)hwnd; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM | MCI_NOTIFY, + (DWORD_PTR)&parm); + ok(!err,"mciCommand MCI_GETDEVCAPS inputs: %s\n", dbg_mcierr(err)); + ok(parm.caps.dwReturn==ndevs,"mciCommand GETDEVCAPS claims %u inputs, expected %u\n", parm.caps.dwReturn, ndevs); + ok(!ok_pcm || !parm.caps.dwReturn,"No input device accepts PCM!?\n"); + test_notification(hwnd, "GETDEVCAPS inputs", MCI_NOTIFY_SUCCESSFUL); + /* A few ME machines pass all tests except set format tag pcm! */ - err = mciSendString("record x to 2000 wait", NULL, 0, hwnd); + err = mciSendStringA("record x to 2000 wait", NULL, 0, hwnd); ok(err || !ok_pcm,"can record yet set wave format pcm returned %s\n", dbg_mcierr(ok_pcm)); - ok(!err || err==(ok_pcm==MCIERR_OUTOFRANGE ? MCIERR_WAVE_INPUTSUNSUITABLE : 0),"mci record to 2000 returned %s\n", dbg_mcierr(err)); + if(!ndevs) todo_wine /* with sound disabled */ + ok(ndevs>0 ? !err : err==MCIERR_WAVE_INPUTSUNSUITABLE,"mci record to 2000 returned %s\n", dbg_mcierr(err)); + else + ok(ndevs>0 ? !err : err==MCIERR_WAVE_INPUTSUNSUITABLE,"mci record to 2000 returned %s\n", dbg_mcierr(err)); if(err) { if (err==MCIERR_WAVE_INPUTSUNSUITABLE) skip("Please install audio driver. Everything is skipped.\n"); else skip("Cannot record cause %s. Everything is skipped.\n", dbg_mcierr(err)); - err = mciSendString("close x", NULL, 0, NULL); + err = mciSendStringA("close x", NULL, 0, NULL); ok(!err,"mci close returned %s\n", dbg_mcierr(err)); test_notification(hwnd,"record skipped",0); return; } /* Query some wave format parameters depending on the time format. */ - err = mciSendString("status x position", buf, sizeof(buf), NULL); + err = mciSendStringA("status x position", buf, sizeof(buf), NULL); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); if(!err) todo_wine ok(!strcmp(buf,"2000"), "mci status position gave %s, expected 2000, some tests will fail\n", buf); - err = mciSendString("set x time format 8", NULL, 0, NULL); /* bytes */ + err = mciSendStringA("set x time format 8", NULL, 0, NULL); /* bytes */ ok(!err,"mci returned %s\n", dbg_mcierr(err)); parm.status.dwItem = MCI_STATUS_POSITION; - err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); ok(!err,"mciCommand status position: %s\n", dbg_mcierr(err)); expect = 2 * nsamp * nch * nbits/8; if(!err) todo_wine ok(parm.status.dwReturn==expect,"recorded %lu bytes, expected %u\n",parm.status.dwReturn,expect); parm.set.dwTimeFormat = MCI_FORMAT_SAMPLES; - err = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm); ok(!err,"mciCommand set time format samples: %s\n", dbg_mcierr(err)); parm.status.dwItem = MCI_STATUS_POSITION; - err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); ok(!err,"mciCommand status position: %s\n", dbg_mcierr(err)); expect = 2 * nsamp; if(!err) todo_wine ok(parm.status.dwReturn==expect,"recorded %lu samples, expected %u\n",parm.status.dwReturn,expect); - err = mciSendString("set x time format milliseconds", NULL, 0, NULL); + err = mciSendStringA("set x time format milliseconds", NULL, 0, NULL); ok(!err,"mci set time format milliseconds returned %s\n", dbg_mcierr(err)); - err = mciSendString("save x tempfile1.wav", NULL, 0, NULL); + err = mciSendStringA("save x tempfile1.wav", NULL, 0, NULL); ok(!err,"mci save returned %s\n", dbg_mcierr(err)); - err = mciSendString("save x tempfile.wav", NULL, 0, NULL); + err = mciSendStringA("save x tempfile.wav", NULL, 0, NULL); ok(!err,"mci save returned %s\n", dbg_mcierr(err)); if(!err) ok_saved = 0; /* Save must not rename the original file. */ - if (!DeleteFile("tempfile1.wav")) - todo_wine ok(FALSE, "Save must not rename the original file; DeleteFile returned %d\n", GetLastError()); + if (!DeleteFileA("tempfile1.wav")) + todo_wine ok(FALSE, "Save must not rename the original file; DeleteFileA returned %d\n", + GetLastError()); - err = mciSendString("set x channels 2", NULL, 0, NULL); + err = mciSendStringA("set x channels 2", NULL, 0, NULL); ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci set channels after saving returned %s\n", dbg_mcierr(err)); parm.seek.dwTo = 600; - err = mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO | MCI_WAIT, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO | MCI_WAIT, (DWORD_PTR)&parm); ok(!err,"mciCommand seek to 600: %s\n", dbg_mcierr(err)); /* Truncate to current position */ - err = mciSendString("delete x", NULL, 0, NULL); + err = mciSendStringA("delete x", NULL, 0, NULL); todo_wine ok(!err,"mci delete returned %s\n", dbg_mcierr(err)); - buf[0]='\0'; - err = mciSendString("status x length", buf, sizeof(buf), NULL); + err = mciSendStringA("status x length", buf, sizeof(buf), NULL); ok(!err,"mci status length returned %s\n", dbg_mcierr(err)); todo_wine ok(!strcmp(buf,"600"), "mci status length after delete gave %s, expected 600\n", buf); - err = mciSendString("close x", NULL, 0, NULL); + err = mciSendStringA("close x", NULL, 0, NULL); ok(!err,"mci close returned %s\n", dbg_mcierr(err)); test_notification(hwnd,"record complete",0); } @@ -404,113 +801,132 @@ static void test_playWAVE(HWND hwnd) char buf[1024]; memset(buf, 0, sizeof(buf)); - err = mciSendString("open waveaudio!tempfile.wav alias mysound", NULL, 0, NULL); + err = mciSendStringA("open waveaudio!tempfile.wav alias mysound", NULL, 0, NULL); ok(err==ok_saved,"mci open waveaudio!tempfile.wav returned %s\n", dbg_mcierr(err)); if(err) { skip("Cannot open waveaudio!tempfile.wav for playing (%s), skipping\n", dbg_mcierr(err)); return; } - err = mciSendString("status mysound length", buf, sizeof(buf), NULL); + err = mciGetDeviceIDA("mysound"); + ok(err == 1, "mciGetDeviceIDA mysound returned %u, expected 1\n", err); + + err = mciGetDeviceIDA("tempfile.wav"); + ok(!err, "mciGetDeviceIDA tempfile.wav returned %u, expected 0\n", err); + + err = mciGetDeviceIDA("waveaudio"); + ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err); + + err = mciSendStringA("status mysound length", buf, sizeof(buf), NULL); ok(!err,"mci status length returned %s\n", dbg_mcierr(err)); todo_wine ok(!strcmp(buf,"2000"), "mci status length gave %s, expected 2000, some tests will fail.\n", buf); - err = mciSendString("cue output", NULL, 0, NULL); - todo_wine ok(err==MCIERR_UNRECOGNIZED_COMMAND,"mci incorrect cue output returned %s\n", dbg_mcierr(err)); + err = mciSendStringA("cue output", NULL, 0, NULL); + ok(err==MCIERR_UNRECOGNIZED_COMMAND,"mci incorrect cue output returned %s\n", dbg_mcierr(err)); /* Test MCI to the bones -- Some todo_wine from Cue and * from Play from 0 to 0 are not worth fixing. */ - err = mciSendString("cue mysound output notify", NULL, 0, hwnd); + err = mciSendStringA("cue mysound output notify", NULL, 0, hwnd); ok(!err,"mci cue output after open file returned %s\n", dbg_mcierr(err)); /* Notification is delayed as a play thread is started. */ - todo_wine test_notification1(hwnd, "cue immediate", 0); + todo_wine test_notification(hwnd, "cue immediate", 0); /* Cue pretends to put the MCI into paused state. */ - err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); todo_wine ok(!strcmp(buf,"paused"), "mci status mode: %s, expected (pseudo)paused\n", buf); /* Strange pause where Pause is rejected, unlike Play; Pause; Pause tested below */ - err = mciSendString("pause mysound", NULL, 0, hwnd); + err = mciSendStringA("pause mysound", NULL, 0, hwnd); ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci pause after cue returned %s\n", dbg_mcierr(err)); /* MCI appears to start the play thread in this border case. * Guessed that from (flaky) status mode and late notification arrival. */ - err = mciSendString("play mysound from 0 to 0 notify", NULL, 0, hwnd); + err = mciSendStringA("play mysound from 0 to 0 notify", NULL, 0, hwnd); ok(!err,"mci play from 0 to 0 returned %s\n", dbg_mcierr(err)); - todo_wine test_notification1(hwnd, "cue aborted by play", MCI_NOTIFY_ABORTED); + todo_wine test_notification(hwnd, "cue aborted by play", MCI_NOTIFY_ABORTED); /* play's own notification follows below */ - err = mciSendString("play mysound from 250 to 0", NULL, 0, NULL); + err = mciSendStringA("play mysound from 250 to 0", NULL, 0, NULL); ok(err==MCIERR_OUTOFRANGE,"mci play from 250 to 0 returned %s\n", dbg_mcierr(err)); Sleep(50); /* Give play from 0 to 0 time to finish. */ - todo_wine test_notification1(hwnd, "play from 0 to 0", MCI_NOTIFY_SUCCESSFUL); + todo_wine test_notification(hwnd, "play from 0 to 0", MCI_NOTIFY_SUCCESSFUL); - err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); ok(!strcmp(buf,"stopped"), "mci status mode: %s after play from 0 to 0\n", buf); - err = mciSendString("play MYSOUND from 250 to 0 notify", NULL, 0, hwnd); + err = mciSendStringA("play MYSOUND from 250 to 0 notify", NULL, 0, hwnd); ok(err==MCIERR_OUTOFRANGE,"mci play from 250 to 0 notify returned %s\n", dbg_mcierr(err)); /* No notification (checked below) sent if error */ /* A second play caused Wine<1.1.33 to hang */ - err = mciSendString("play mysound from 500 to 1500 wait", NULL, 0, NULL); - ok(!err,"mci play from 500 to 1500 returned %s\n", dbg_mcierr(err)); + err = mciSendStringA("play mysound from 500 to 220:5:0 wait", NULL, 0, NULL); + ok(!err,"mci play from 500 to 220:5:0 (=1500) returned %s\n", dbg_mcierr(err)); - memset(buf, 0, sizeof(buf)); - err = mciSendString("status mysound position", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"1500"), "mci status position: %s\n", buf); /* mci will not play position < current */ - err = mciSendString("play mysound to 1000", NULL, 0, NULL); + err = mciSendStringA("play mysound to 1000", NULL, 0, NULL); ok(err==MCIERR_OUTOFRANGE,"mci play to 1000 returned %s\n", dbg_mcierr(err)); /* mci will not play to > end */ - err = mciSendString("play mysound TO 3000 notify", NULL, 0, hwnd); + err = mciSendStringA("play mysound TO 3000 notify", NULL, 0, hwnd); ok(err==MCIERR_OUTOFRANGE,"mci play to 3000 notify returned %s\n", dbg_mcierr(err)); - err = mciSendString("play mysound to 2000", NULL, 0, NULL); + err = mciSendStringA("play mysound to 2000", NULL, 0, NULL); ok(!err,"mci play to 2000 returned %s\n", dbg_mcierr(err)); /* Rejected while playing */ - err = mciSendString("cue mysound output", NULL, 0, NULL); + err = mciSendStringA("cue mysound output", NULL, 0, NULL); ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci cue output while playing returned %s\n", dbg_mcierr(err)); - err = mciSendString("play mysound to 3000", NULL, 0, NULL); + err = mciSendStringA("play mysound to 3000", NULL, 0, NULL); ok(err==MCIERR_OUTOFRANGE,"mci play to 3000 returned %s\n", dbg_mcierr(err)); - err = mciSendString("stop mysound Wait", NULL, 0, NULL); + err = mciSendStringA("stop mysound Wait", NULL, 0, NULL); ok(!err,"mci stop wait returned %s\n", dbg_mcierr(err)); test_notification(hwnd, "play/cue/pause/stop", 0); - err = mciSendString("Seek Mysound to 250 wait Notify", NULL, 0, hwnd); + err = mciSendStringA("Seek Mysound to 250 wait Notify", NULL, 0, hwnd); ok(!err,"mci seek to 250 wait notify returned %s\n", dbg_mcierr(err)); test_notification(hwnd,"seek wait notify",MCI_NOTIFY_SUCCESSFUL); - memset(buf, 0, sizeof(buf)); - err = mciSendString("status mysound position notify", buf, sizeof(buf), hwnd); + err = mciSendStringA("seek mysound to 0xfa", NULL, 0, NULL); + ok(err==MCIERR_BAD_INTEGER,"mci seek to 0xfa returned %s\n", dbg_mcierr(err)); + + /* MCI_INTEGER always accepts colon notation */ + err = mciSendStringA("seek mysound to :1", NULL, 0, NULL); + ok(!err,"mci seek to :1 (=256) returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek mysound to 250::", NULL, 0, NULL); + ok(!err,"mci seek to 250:: returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek mysound to 250:0", NULL, 0, NULL); + ok(!err,"mci seek to 250:0 returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status mysound position notify", buf, sizeof(buf), hwnd); ok(!err,"mci status position notify returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"250"), "mci status position: %s\n", buf); /* Immediate commands like status also send notifications. */ test_notification(hwnd,"status position",MCI_NOTIFY_SUCCESSFUL); - memset(buf, 0, sizeof(buf)); - err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf); /* Another play from == to testcase */ - err = mciSendString("play mysound to 250 wait notify", NULL, 0, hwnd); + err = mciSendStringA("play mysound to 250 wait notify", NULL, 0, hwnd); ok(!err,"mci play (from 250) to 250 returned %s\n", dbg_mcierr(err)); - todo_wine test_notification1(hwnd,"play to 250 wait notify",MCI_NOTIFY_SUCCESSFUL); + todo_wine test_notification(hwnd,"play to 250 wait notify",MCI_NOTIFY_SUCCESSFUL); - err = mciSendString("cue mysound output", NULL, 0, NULL); + err = mciSendStringA("cue mysound output", NULL, 0, NULL); ok(!err,"mci cue output after play returned %s\n", dbg_mcierr(err)); - err = mciSendString("close mysound", NULL, 0, NULL); + err = mciSendStringA("close mysound", NULL, 0, NULL); ok(!err,"mci close returned %s\n", dbg_mcierr(err)); test_notification(hwnd,"after close",0); } @@ -523,7 +939,7 @@ static void test_asyncWAVE(HWND hwnd) char buf[1024]; memset(buf, 0, sizeof(buf)); - err = mciSendString("open tempfile.wav alias mysound notify", buf, sizeof(buf), hwnd); + err = mciSendStringA("open tempfile.wav alias mysound notify type waveaudio", buf, sizeof(buf), hwnd); ok(err==ok_saved,"mci open tempfile.wav returned %s\n", dbg_mcierr(err)); if(err) { skip("Cannot open tempfile.wav for playing (%s), skipping\n", dbg_mcierr(err)); @@ -534,60 +950,71 @@ static void test_asyncWAVE(HWND hwnd) ok(wDeviceID,"mci open DeviceID: %d\n", wDeviceID); test_notification(hwnd,"open alias notify",MCI_NOTIFY_SUCCESSFUL); - err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd); + err = mciGetDeviceIDA("mysound"); + ok(err == wDeviceID, "mciGetDeviceIDA alias returned %u, expected %u\n", err, wDeviceID); + + /* Only the alias is looked up. */ + err = mciGetDeviceIDA("tempfile.wav"); + ok(!err, "mciGetDeviceIDA tempfile.wav returned %u, expected 0\n", err); + + err = mciGetDeviceIDA("waveaudio"); + ok(!err, "mciGetDeviceIDA waveaudio returned %u, expected 0\n", err); + + err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf); - err = mciSendString("play mysound notify", NULL, 0, hwnd); + err = mciSendStringA("play mysound notify", NULL, 0, hwnd); ok(!err,"mci play returned %s\n", dbg_mcierr(err)); - /* Give Wine's asynchronous thread time to start up. Furthermore, - * it uses 3 buffers per second, so that the positions reported - * will be 333ms, 667ms etc. at best. */ - Sleep(100); /* milliseconds */ + Sleep(500); /* milliseconds */ /* Do not query time format as string because result depends on locale! */ parm.status.dwItem = MCI_STATUS_TIME_FORMAT; - err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); ok(!err,"mciCommand status time format: %s\n", dbg_mcierr(err)); if(!err) ok(parm.status.dwReturn==MCI_FORMAT_MILLISECONDS,"status time format: %ld\n",parm.status.dwReturn); parm.set.dwTimeFormat = MCI_FORMAT_MILLISECONDS; - err = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm); + err = mciSendCommandA(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&parm); ok(!err,"mciCommand set time format ms: %s\n", dbg_mcierr(err)); - buf[0]=0; - err = mciSendString("status mysound position", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); - ok(strcmp(buf,"2000"), "mci status position: %s, expected 2000\n", buf); - trace("position after Sleep: %sms\n",buf); + trace("position after Sleep: %sms\n", buf); p2 = atoi(buf); - /* Some machines reach 79ms only during the 100ms sleep. */ - ok(p2>=67,"not enough time elapsed %ums\n",p2); + /* Check that the 2s sound plays at a normal pace, giving a wide margin to + * account for timing granularity and small delays. + */ + todo_wine ok(350 <= p2 && p2 <= 600, "%ums is not in the expected 350-600ms range\n", p2); + /* Wine's asynchronous thread needs some time to start up. Furthermore, it + * uses 3 buffers per second, so that the positions reported will be 333ms, + * 667ms etc. at best, which is why it fails the above test. So add a + * second test specifically to prevent Wine from getting even worse. + * FIXME: To be removed when Wine is fixed and passes the above test. + */ + ok(350 <= p2 && p2 <= 1000, "%ums is not even in the expected 350-1000ms range\n", p2); test_notification(hwnd,"play (nowait)",0); - err = mciSendString("pause mysound wait", NULL, 0, hwnd); + err = mciSendStringA("pause mysound wait", NULL, 0, hwnd); ok(!err,"mci pause wait returned %s\n", dbg_mcierr(err)); - buf[0]=0; - err = mciSendString("status mysound mode notify", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound mode notify", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"paused"), "mci status mode: %s\n", buf); test_notification(hwnd,"play",MCI_NOTIFY_SUPERSEDED); test_notification(hwnd,"status",MCI_NOTIFY_SUCCESSFUL); - buf[0]=0; - err = mciSendString("status mysound position", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); trace("position while paused: %sms\n",buf); p1 = atoi(buf); ok(p1>=p2, "position not increasing: %u > %u\n", p2, p1); - err = mciSendString("stop mysound wait", NULL, 0, NULL); + err = mciSendStringA("stop mysound wait", NULL, 0, NULL); ok(!err,"mci stop returned %s\n", dbg_mcierr(err)); - buf[0]=0; - err = mciSendString("info mysound file notify", buf, sizeof(buf), hwnd); + err = mciSendStringA("info mysound file notify", buf, sizeof(buf), hwnd); ok(!err,"mci info file returned %s\n", dbg_mcierr(err)); if(!err) { /* fully qualified name */ int len = strlen(buf); @@ -596,13 +1023,11 @@ static void test_asyncWAVE(HWND hwnd) } test_notification(hwnd,"info file",MCI_NOTIFY_SUCCESSFUL); - buf[0]=0; - err = mciSendString("status mysound mode", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound mode", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf); - buf[0]=0; - err = mciSendString("status mysound position", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); trace("position once stopped: %sms\n",buf); p2 = atoi(buf); @@ -610,84 +1035,82 @@ static void test_asyncWAVE(HWND hwnd) ok(p2>=p1 && p2<=p1+16,"position changed from %ums to %ums\n",p1,p2); /* No Resume once stopped (waveaudio, sequencer and cdaudio differ). */ - err = mciSendString("resume mysound wait", NULL, 0, NULL); + err = mciSendStringA("resume mysound wait", NULL, 0, NULL); ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci resume wait returned %s\n", dbg_mcierr(err)); - err = mciSendString("play mysound wait", NULL, 0, NULL); + err = mciSendStringA("play mysound wait", NULL, 0, NULL); ok(!err,"mci play wait returned %s\n", dbg_mcierr(err)); - buf[0]=0; - err = mciSendString("status mysound position", buf, sizeof(buf), hwnd); + err = mciSendStringA("status mysound position", buf, sizeof(buf), hwnd); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); todo_wine ok(!strcmp(buf,"2000"), "mci status position: %s\n", buf); - err = mciSendString("seek mysound to start wait", NULL, 0, NULL); + err = mciSendStringA("seek mysound to start wait", NULL, 0, NULL); ok(!err,"mci seek to start wait returned %s\n", dbg_mcierr(err)); - err = mciSendString("play mysound to 1000 notify", NULL, 0, hwnd); + err = mciSendStringA("play mysound to 1000 notify", NULL, 0, hwnd); ok(!err,"mci play returned %s\n", dbg_mcierr(err)); /* Sleep(200); not needed with Wine any more. */ - err = mciSendString("pause mysound notify", NULL, 0, NULL); /* notify no callback */ + err = mciSendStringA("pause mysound notify", NULL, 0, NULL); /* notify no callback */ ok(!err,"mci pause notify returned %s\n", dbg_mcierr(err)); /* Supersede even though pause cannot notify given no callback */ test_notification(hwnd,"pause aborted play #1 notification",MCI_NOTIFY_SUPERSEDED); test_notification(hwnd,"impossible pause notification",0); - err = mciSendString("cue mysound output notify", NULL, 0, hwnd); + err = mciSendStringA("cue mysound output notify", NULL, 0, hwnd); ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci cue output while paused returned %s\n", dbg_mcierr(err)); test_notification(hwnd,"cue output notify #2",0); - err = mciSendString("resume mysound notify", NULL, 0, hwnd); + err = mciSendStringA("resume mysound notify", NULL, 0, hwnd); ok(!err,"mci resume notify returned %s\n", dbg_mcierr(err)); test_notification(hwnd, "resume notify", MCI_NOTIFY_SUCCESSFUL); /* Seek or even Stop used to hang Wine<1.1.32 on MacOS. */ - err = mciSendString("seek mysound to 0 wait", NULL, 0, NULL); + err = mciSendStringA("seek mysound to 0 wait", NULL, 0, NULL); ok(!err,"mci seek to start returned %s\n", dbg_mcierr(err)); /* Seek stops. */ - err = mciSendString("status mysound mode", buf, sizeof(buf), NULL); + err = mciSendStringA("status mysound mode", buf, sizeof(buf), NULL); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"stopped"), "mci status mode: %s\n", buf); - err = mciSendString("seek mysound wait", NULL, 0, NULL); + err = mciSendStringA("seek mysound wait", NULL, 0, NULL); ok(err==MCIERR_MISSING_PARAMETER,"mci seek to nowhere returned %s\n", dbg_mcierr(err)); /* cdaudio does not detect to start to end as error */ - err = mciSendString("seek mysound to start to 0", NULL, 0, NULL); + err = mciSendStringA("seek mysound to start to 0", NULL, 0, NULL); ok(err==MCIERR_FLAGS_NOT_COMPATIBLE,"mci seek to start to 0 returned %s\n", dbg_mcierr(err)); - err = mciSendString("PLAY mysound to 1000 notify", NULL, 0, hwnd); + err = mciSendStringA("PLAY mysound to 1000 notify", NULL, 0, hwnd); ok(!err,"mci play to 1000 notify returned %s\n", dbg_mcierr(err)); /* Sleep(200); not needed with Wine any more. */ /* Give it 400ms and resume will appear to complete below. */ - err = mciSendString("pause mysound wait", NULL, 0, NULL); + err = mciSendStringA("pause mysound wait", NULL, 0, NULL); ok(!err,"mci pause wait returned %s\n", dbg_mcierr(err)); /* Unlike sequencer and cdaudio, waveaudio's pause does not abort. */ test_notification(hwnd,"pause aborted play #2 notification",0); - err = mciSendString("resume mysound wait", NULL, 0, NULL); + err = mciSendStringA("resume mysound wait", NULL, 0, NULL); ok(!err,"mci resume wait returned %s\n", dbg_mcierr(err)); /* Resume is a short asynchronous call, something else is playing. */ - err = mciSendString("status mysound mode", buf, sizeof(buf), NULL); + err = mciSendStringA("status mysound mode", buf, sizeof(buf), NULL); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"playing"), "mci status mode: %s\n", buf); /* Note extra space before alias */ - err = mciSendString("pause mysound wait", NULL, 0, NULL); + err = mciSendStringA("pause mysound wait", NULL, 0, NULL); todo_wine ok(!err,"mci pause (space) wait returned %s\n", dbg_mcierr(err)); - err = mciSendString("pause mysound wait", NULL, 0, NULL); + err = mciSendStringA("pause mysound wait", NULL, 0, NULL); ok(!err,"mci pause wait returned %s\n", dbg_mcierr(err)); /* Better ask position only when paused, is it updated while playing? */ - buf[0]='\0'; - err = mciSendString("status mysound position", buf, sizeof(buf), NULL); + err = mciSendStringA("status mysound position", buf, sizeof(buf), NULL); ok(!err,"mci status position returned %s\n", dbg_mcierr(err)); /* TODO compare position < 900 */ ok(strcmp(buf,"1000"), "mci resume waited\n"); @@ -695,7 +1118,7 @@ static void test_asyncWAVE(HWND hwnd) trace("position after resume: %sms\n",buf); test_notification(hwnd,"play (aborted by pause/resume/pause)",0); - err = mciSendString("close mysound wait", NULL, 0, NULL); + err = mciSendStringA("close mysound wait", NULL, 0, NULL); ok(!err,"mci close wait returned %s\n", dbg_mcierr(err)); test_notification(hwnd,"play (aborted by close)",MCI_NOTIFY_ABORTED); } @@ -705,43 +1128,50 @@ static void test_AutoOpenWAVE(HWND hwnd) /* This test used(?) to cause intermittent crashes when Wine exits, after * fixme:winmm:MMDRV_Exit Closing while ll-driver open */ - MCIERROR err, ok_snd; + UINT ndevs = waveOutGetNumDevs(); + MCIERROR err, ok_snd = ndevs ? 0 : MCIERR_HARDWARE; + MCI_PARMS_UNION parm; char buf[512], path[300], command[330]; + DWORD intbuf[3] = { 0xDEADF00D, 99, 0xABADCAFE }; memset(buf, 0, sizeof(buf)); memset(path, 0, sizeof(path)); /* Do not crash on NULL buffer pointer */ - err = mciSendString("sysinfo waveaudio quantity open", NULL, 0, NULL); + err = mciSendStringA("sysinfo waveaudio quantity open", NULL, 0, NULL); ok(err==MCIERR_PARAM_OVERFLOW,"mci sysinfo without buffer returned %s\n", dbg_mcierr(err)); - err = mciSendString("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL); + err = mciSendStringA("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL); ok(!err,"mci sysinfo waveaudio quantity open returned %s\n", dbg_mcierr(err)); - if(!err) todo_wine ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0, got: %s, some more tests will fail.\n", buf); + if(!err) ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0, got: %s, some more tests will fail.\n", buf); - ok_snd = waveOutGetNumDevs() ? 0 : MCIERR_HARDWARE; - err = mciSendString("sound NoSuchSoundDefined wait", NULL, 0, NULL); - todo_wine ok(err==ok_snd,"mci sound NoSuchSoundDefined returned %s\n", dbg_mcierr(err)); + /* Who knows why some MS machines pass all tests but return MCIERR_HARDWARE here? */ + /* Wine returns MCIERR_HARDWARE when no default sound is found in win.ini or the registry. */ + err = mciSendStringA("sound NoSuchSoundDefined wait", NULL, 0, NULL); + ok(err==ok_snd || err==MCIERR_HARDWARE, "mci sound NoSuchSoundDefined returned %s\n", dbg_mcierr(err)); - err = mciSendString("sound SystemExclamation notify wait", NULL, 0, hwnd); - todo_wine ok(err==ok_snd,"mci sound SystemExclamation returned %s\n", dbg_mcierr(err)); + err = mciSendStringA("sound SystemExclamation notify wait", NULL, 0, hwnd); + ok(err==ok_snd || err==MCIERR_HARDWARE, "mci sound SystemExclamation returned %s\n", dbg_mcierr(err)); test_notification(hwnd, "sound notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL); - buf[0]=0; - err = mciSendString("sysinfo waveaudio name 1 open", buf, sizeof(buf), NULL); - todo_wine ok(err==MCIERR_OUTOFRANGE,"sysinfo waveaudio name 1 returned %s\n", dbg_mcierr(err)); + Sleep(16); /* time to auto-close makes sysinfo below return expected error */ + err = mciSendStringA("sysinfo waveaudio notify name 1 open", buf, sizeof(buf), hwnd); + ok(err==MCIERR_OUTOFRANGE,"sysinfo waveaudio name 1 returned %s\n", dbg_mcierr(err)); if(!err) trace("sysinfo dangling open alias: %s\n", buf); + test_notification(hwnd, "sysinfo name outofrange\n", err ? 0 : MCI_NOTIFY_SUCCESSFUL); - err = mciSendString("play no-such-file-exists.wav notify", buf, sizeof(buf), NULL); - if(err==MCIERR_FILE_NOT_FOUND) { /* a Wine detector */ - /* Unsupported auto-open leaves the file open, preventing clean-up */ - skip("Skipping auto-open tests in Wine\n"); - return; - } + err = mciSendStringA("play no-such-file-exists.wav notify", buf, sizeof(buf), NULL); + todo_wine ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open notify returned %s\n", dbg_mcierr(err)); + /* FILE_NOT_FOUND in Wine because auto-open fails before testing the notify flag */ - err = mciSendString("play tempfile.wav notify", buf, sizeof(buf), hwnd); - todo_wine ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open play notify returned %s\n", dbg_mcierr(err)); + test_notification(hwnd, "-prior to auto-open-", 0); + + err = mciSendStringA("play tempfile.wav notify", buf, sizeof(buf), hwnd); + if(ok_saved==MCIERR_FILE_NOT_FOUND) todo_wine /* same as above */ + ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open play notify returned %s\n", dbg_mcierr(err)); + else + ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN,"mci auto-open play notify returned %s\n", dbg_mcierr(err)); if(err) /* FIXME: don't open twice yet, it confuses Wine. */ - err = mciSendString("play tempfile.wav", buf, sizeof(buf), hwnd); + err = mciSendStringA("play tempfile.wav", buf, sizeof(buf), hwnd); ok(err==ok_saved,"mci auto-open play returned %s\n", dbg_mcierr(err)); if(err==MCIERR_FILE_NOT_FOUND) { @@ -749,36 +1179,47 @@ static void test_AutoOpenWAVE(HWND hwnd) return; } - buf[0]=0; - err = mciSendString("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL); + err = mciSendStringA("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL); ok(!err,"mci sysinfo waveaudio quantity after auto-open returned %s\n", dbg_mcierr(err)); - if(!err) todo_wine ok(!strcmp(buf,"1"), "sysinfo quantity open expected 1, got: %s\n", buf); + if(!err) ok(!strcmp(buf,"1"), "sysinfo quantity open expected 1, got: %s\n", buf); - buf[0]=0; - err = mciSendString("sysinfo waveaudio name 1 open", buf, sizeof(buf), NULL); - todo_wine ok(!err,"mci sysinfo waveaudio name after auto-open returned %s\n", dbg_mcierr(err)); + parm.sys.lpstrReturn = (LPSTR)&intbuf[1]; + parm.sys.dwRetSize = 2*sizeof(DWORD); /* only one DWORD is used */ + parm.sys.wDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; + err = mciSendCommandA(0, MCI_SYSINFO, MCI_SYSINFO_QUANTITY | MCI_SYSINFO_OPEN, (DWORD_PTR)&parm); + ok(!err, "mciCommand sysinfo waveaudio open notify returned %s\n", dbg_mcierr(err)); + if(!err) ok(atoi(buf)==intbuf[1],"sysinfo waveaudio quantity open string and command differ\n"); + + err = mciSendStringA("sysinfo waveaudio name 1 open notify", buf, sizeof(buf), hwnd); + ok(!err,"mci sysinfo waveaudio name after auto-open returned %s\n", dbg_mcierr(err)); /* This is the alias, not necessarily a file name. */ if(!err) ok(!strcmp(buf,"tempfile.wav"), "sysinfo name 1 open: %s\n", buf); + test_notification(hwnd, "sysinfo name notify\n", MCI_NOTIFY_SUCCESSFUL); + + err = mciGetDeviceIDA("tempfile.wav"); + ok(err == 1, "mciGetDeviceIDA tempfile.wav returned %u, expected 1\n", err); /* Save the full pathname to the file. */ - err = mciSendString("info tempfile.wav file", path, sizeof(path), NULL); + err = mciSendStringA("info tempfile.wav file", path, sizeof(path), NULL); ok(!err,"mci info tempfile.wav file returned %s\n", dbg_mcierr(err)); if(err) strcpy(path,"tempfile.wav"); - err = mciSendString("status tempfile.wav mode", NULL, 0, hwnd); + err = mciSendStringA("status tempfile.wav mode", NULL, 0, hwnd); ok(!err,"mci status tempfile.wav mode without buffer returned %s\n", dbg_mcierr(err)); sprintf(command,"status \"%s\" mode",path); - err = mciSendString(command, buf, sizeof(buf), hwnd); + err = mciSendStringA(command, buf, sizeof(buf), hwnd); ok(!err,"mci status \"%s\" mode returned %s\n", path, dbg_mcierr(err)); - buf[0]=0; - err = mciSendString("status tempfile.wav mode", buf, sizeof(buf), hwnd); + err = mciSendStringA("status tempfile.wav mode", buf, sizeof(buf), hwnd); ok(!err,"mci status tempfile.wav mode returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"playing"), "mci auto-open status mode, got: %s\n", buf); - err = mciSendString("open tempfile.wav", buf, sizeof(buf), NULL); - todo_wine ok(err==MCIERR_DEVICE_OPEN, "mci open from auto-open returned %s\n", dbg_mcierr(err)); + err = mciSendStringA("open tempfile.wav", buf, sizeof(buf), NULL); + ok(err==MCIERR_DEVICE_OPEN, "mci open from auto-open returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("open foo.wav alias tempfile.wav", buf, sizeof(buf), NULL); + ok(err==MCIERR_DUPLICATE_ALIAS, "mci open re-using alias returned %s\n", dbg_mcierr(err)); /* w2k/xp and Wine differ. While the device is busy playing, it is * regularly open and accessible via the filename: subsequent @@ -791,71 +1232,134 @@ static void test_AutoOpenWAVE(HWND hwnd) * MCIERR_NOTIFY_ON_AUTO_OPEN and thus don't abort the original * command. */ - err = mciSendString("status tempfile.wav mode notify", buf, sizeof(buf), hwnd); + err = mciSendStringA("status tempfile.wav mode notify", buf, sizeof(buf), hwnd); todo_wine ok(err==MCIERR_NOTIFY_ON_AUTO_OPEN, "mci status auto-open notify returned %s\n", dbg_mcierr(err)); if(!err) { trace("Wine style MCI auto-close upon notification\n"); /* "playing" because auto-close comes after the status call. */ - todo_wine ok(!strcmp(buf,"playing"), "mci auto-open status mode notify, got: %s\n", buf); + ok(!strcmp(buf,"playing"), "mci auto-open status mode notify, got: %s\n", buf); /* fixme:winmm:MMDRV_Exit Closing while ll-driver open * is explained by failure to auto-close a device. */ test_notification(hwnd,"status notify",MCI_NOTIFY_SUCCESSFUL); /* MCI received NOTIFY_SUPERSEDED and auto-closed the device. */ + + /* Until this is implemented, force closing the device */ + err = mciSendStringA("close tempfile.wav", NULL, 0, hwnd); + ok(!err,"mci auto-still-open stop returned %s\n", dbg_mcierr(err)); Sleep(16); test_notification(hwnd,"auto-open",0); } else if(err==MCIERR_NOTIFY_ON_AUTO_OPEN) { /* MS style */ trace("MS style MCI auto-open forbids notification\n"); - err = mciSendString("pause tempfile.wav", NULL, 0, hwnd); + err = mciSendStringA("pause tempfile.wav", NULL, 0, hwnd); ok(!err,"mci auto-still-open pause returned %s\n", dbg_mcierr(err)); - err = mciSendString("status tempfile.wav mode", buf, sizeof(buf), hwnd); + err = mciSendStringA("status tempfile.wav mode", buf, sizeof(buf), hwnd); ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"paused"), "mci auto-open status mode, got: %s\n", buf); /* Auto-close */ - err = mciSendString("stop tempfile.wav", NULL, 0, hwnd); + err = mciSendStringA("stop tempfile.wav wait", NULL, 0, hwnd); ok(!err,"mci auto-still-open stop returned %s\n", dbg_mcierr(err)); Sleep(16); /* makes sysinfo quantity open below succeed */ } - err = mciSendString("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL); + err = mciSendStringA("sysinfo waveaudio quantity open", buf, sizeof(buf), NULL); ok(!err,"mci sysinfo waveaudio quantity open after close returned %s\n", dbg_mcierr(err)); - if(!err) todo_wine ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0 after auto-close, got: %s\n", buf); + if(!err) ok(!strcmp(buf,"0"), "sysinfo quantity open expected 0 after auto-close, got: %s\n", buf); /* w95-WinME (not w2k/XP) switch to C:\ after auto-playing once. Prevent * MCIERR_FILE_NOT_FOUND by using the full path name from the Info file command. */ sprintf(command,"status \"%s\" mode wait",path); - err = mciSendString(command, buf, sizeof(buf), hwnd); + err = mciSendStringA(command, buf, sizeof(buf), hwnd); ok(!err,"mci re-auto-open status mode returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"stopped"), "mci re-auto-open status mode, got: %s\n", buf); - err = mciSendString("capability waveaudio device type", buf, sizeof(buf), hwnd); + /* This uses auto-open as well. */ + err = mciSendStringA("capability waveaudio outputs", buf, sizeof(buf), NULL); + ok(!err,"mci capability waveaudio outputs returned %s\n", dbg_mcierr(err)); + /* Wine with no sound selected in winecfg's audio tab fails this test. */ + if(!err) ok(atoi(buf)==ndevs,"Expected %d audio outputs, got %s\n", ndevs, buf); + + err = mciSendStringA("capability waveaudio device type", buf, sizeof(buf), hwnd); ok(!err,"mci capability device type returned %s\n", dbg_mcierr(err)); if(!err) ok(!strcmp(buf,"waveaudio"), "mci capability device type response: %s\n", buf); /* waveaudio forbids Pause without Play. */ sprintf(command,"pause \"%s\"",path); - err = mciSendString(command, NULL, 0, hwnd); + err = mciSendStringA(command, NULL, 0, hwnd); ok(err==MCIERR_NONAPPLICABLE_FUNCTION,"mci auto-open pause returned %s\n", dbg_mcierr(err)); + + ok(0xDEADF00D==intbuf[0] && 0xABADCAFE==intbuf[2],"DWORD buffer corruption\n"); +} + +static void test_playWaveTypeMpegvideo(void) +{ + MCIERROR err; + MCIDEVICEID wDeviceID; + MCI_PLAY_PARMS play_parm; + MCI_STATUS_PARMS status_parm; + char buf[1024]; + memset(buf, 0, sizeof(buf)); + + err = mciSendStringA("open tempfile.wav type MPEGVideo alias mysound", NULL, 0, NULL); + ok(err==ok_saved,"mci open tempfile.wav type MPEGVideo returned %s\n", dbg_mcierr(err)); + if(err) { + skip("Cannot open tempfile.wav type MPEGVideo for playing (%s), skipping\n", dbg_mcierr(err)); + return; + } + + wDeviceID = mciGetDeviceIDA("mysound"); + ok(wDeviceID == 1, "mciGetDeviceIDA mysound returned %u, expected 1\n", wDeviceID); + + err = mciSendCommandA(wDeviceID, MCI_PLAY, 0, (DWORD_PTR)&play_parm); + ok(!err,"mciCommand play returned %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status mysound mode", buf, sizeof(buf), NULL); + ok(!err,"mci status mode returned %s\n", dbg_mcierr(err)); + ok(!strcmp(buf,"playing"), "mci status mode: %s\n", buf); + + status_parm.dwItem = MCI_STATUS_MODE; + err = mciSendCommandA(wDeviceID, MCI_STATUS, + MCI_STATUS_ITEM, + (DWORD_PTR)&status_parm); + ok(!err,"mciCommand status mode returned %s\n", dbg_mcierr(err)); + ok(status_parm.dwReturn == MCI_MODE_PLAY, + "mciCommand status mode: %u\n", (DWORD)status_parm.dwReturn); + + err = mciSendStringA("close mysound", NULL, 0, NULL); + ok(!err,"mci close returned %s\n", dbg_mcierr(err)); } START_TEST(mci) { + char curdir[MAX_PATH], tmpdir[MAX_PATH]; MCIERROR err; HWND hwnd; + + GetCurrentDirectoryA(MAX_PATH, curdir); + GetTempPathA(MAX_PATH, tmpdir); + SetCurrentDirectoryA(tmpdir); + hwnd = CreateWindowExA(0, "static", "winmm test", WS_POPUP, 0,0,100,100, 0, 0, 0, NULL); + test_mciParser(hwnd); test_openCloseWAVE(hwnd); test_recordWAVE(hwnd); - test_playWAVE(hwnd); - test_asyncWAVE(hwnd); - test_AutoOpenWAVE(hwnd); + if(waveOutGetNumDevs()){ + test_playWAVE(hwnd); + test_asyncWAVE(hwnd); + test_AutoOpenWAVE(hwnd); + test_playWaveTypeMpegvideo(); + }else + skip("No output devices available, skipping all output tests\n"); /* Win9X hangs when exiting with something still open. */ - err = mciSendString("close all", NULL, 0, hwnd); - todo_wine ok(!err,"final close all returned %s\n", dbg_mcierr(err)); - ok(DeleteFile("tempfile.wav")||ok_saved,"Delete tempfile.wav (cause auto-open?)\n"); + err = mciSendStringA("close all", NULL, 0, hwnd); + ok(!err,"final close all returned %s\n", dbg_mcierr(err)); + ok(DeleteFileA("tempfile.wav") || ok_saved, "Delete tempfile.wav (cause auto-open?)\n"); DestroyWindow(hwnd); + + SetCurrentDirectoryA(curdir); } diff --git a/rostests/winetests/winmm/mcicda.c b/rostests/winetests/winmm/mcicda.c new file mode 100644 index 00000000000..f6e126d29d1 --- /dev/null +++ b/rostests/winetests/winmm/mcicda.c @@ -0,0 +1,612 @@ +/* + * Test MCI CD-ROM access + * + * Copyright 2010 Jörg Höhle + * + * 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 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include "windows.h" +#include "mmsystem.h" +#include "wine/test.h" + +typedef union { + MCI_STATUS_PARMS status; + MCI_GETDEVCAPS_PARMS caps; + MCI_OPEN_PARMSA open; + MCI_PLAY_PARMS play; + MCI_SEEK_PARMS seek; + MCI_SAVE_PARMSA save; + MCI_GENERIC_PARMS gen; + } MCI_PARMS_UNION; + +extern const char* dbg_mcierr(MCIERROR err); /* from mci.c */ + +static BOOL spurious_message(LPMSG msg) +{ + /* WM_DEVICECHANGE 0x0219 appears randomly */ + if(msg->message != MM_MCINOTIFY) { + trace("skipping spurious message %04x\n",msg->message); + return TRUE; + } + return FALSE; +} + +/* A single ok() in each code path allows us to prefix this with todo_wine */ +#define test_notification(hwnd, command, type) test_notification_dbg(hwnd, command, type, __LINE__) +static void test_notification_dbg(HWND hwnd, const char* command, WPARAM type, int line) +{ /* Use type 0 as meaning no message */ + MSG msg; + BOOL seen; + do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); } + while(seen && spurious_message(&msg)); + if(type && !seen) { + /* We observe transient delayed notification, mostly on native. + * Notification is not always present right when mciSend returns. */ + trace_(__FILE__,line)("Waiting for delayed notification from %s\n", command); + MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE); + seen = PeekMessageA(&msg, hwnd, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE); + } + if(!seen) + ok_(__FILE__,line)(type==0, "Expect message %04lx from %s\n", type, command); + else if(msg.hwnd != hwnd) + ok_(__FILE__,line)(msg.hwnd == hwnd, "Didn't get the handle to our test window\n"); + else if(msg.message != MM_MCINOTIFY) + ok_(__FILE__,line)(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command); + else ok_(__FILE__,line)(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command); +} + +#define CDFRAMES_PERSEC 75 +static DWORD MSF_Add(DWORD d1, DWORD d2) +{ + WORD c, m, s, f; + f = MCI_MSF_FRAME(d1) + MCI_MSF_FRAME(d2); + c = f / CDFRAMES_PERSEC; + f = f % CDFRAMES_PERSEC; + s = MCI_MSF_SECOND(d1) + MCI_MSF_SECOND(d2) + c; + c = s / 60; + s = s % 60; + m = MCI_MSF_MINUTE(d1) + MCI_MSF_MINUTE(d2) + c; /* may be > 60 */ + return MCI_MAKE_MSF(m,s,f); +} + +static MCIERROR ok_open = 0; /* MCIERR_CANNOT_LOAD_DRIVER */ + +/* TODO show that shareable flag is not what Wine implements. */ + +static void test_play(HWND hwnd) +{ + MCIDEVICEID wDeviceID; + MCI_PARMS_UNION parm; + MCIERROR err, ok_hw; + DWORD numtracks, track, duration; + DWORD factor = winetest_interactive ? 3 : 1; + char buf[1024]; + memset(buf, 0, sizeof(buf)); + parm.gen.dwCallback = (DWORD_PTR)hwnd; /* once to rule them all */ + + err = mciSendStringA("open cdaudio alias c notify shareable", buf, sizeof(buf), hwnd); + ok(!err || err == MCIERR_CANNOT_LOAD_DRIVER || err == MCIERR_MUST_USE_SHAREABLE, + "mci open cdaudio notify returned %s\n", dbg_mcierr(err)); + ok_open = err; + test_notification(hwnd, "open alias notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + /* Native returns MUST_USE_SHAREABLE when there's trouble with the hardware + * (e.g. unreadable disk) or when Media Player already has the device open, + * yet adding that flag does not help get past this error. */ + + if(err) { + skip("Cannot open any cdaudio device, %s.\n", dbg_mcierr(err)); + return; + } + wDeviceID = atoi(buf); + ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf); + /* Win9X-ME may start the MCI and media player upon insertion of a CD. */ + + err = mciSendStringA("sysinfo all name 1 open", buf, sizeof(buf), NULL); + ok(!err,"sysinfo all name 1 returned %s\n", dbg_mcierr(err)); + if(!err && wDeviceID != 1) trace("Device '%s' is open.\n", buf); + + err = mciSendStringA("capability c has video notify", buf, sizeof(buf), hwnd); + ok(!err, "capability video: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf); + test_notification(hwnd, "capability notify", MCI_NOTIFY_SUCCESSFUL); + + err = mciSendStringA("capability c can play", buf, sizeof(buf), hwnd); + ok(!err, "capability video: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "true"), "capability play is %s\n", buf); + + err = mciSendStringA("capability c", buf, sizeof(buf), NULL); + ok(err == MCIERR_MISSING_PARAMETER, "capability nokeyword: %s\n", dbg_mcierr(err)); + + parm.caps.dwItem = 0x4001; + parm.caps.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm); + ok(err == MCIERR_UNSUPPORTED_FUNCTION, "GETDEVCAPS %x: %s\n", parm.caps.dwItem, dbg_mcierr(err)); + + parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE; + err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm); + ok(!err, "GETDEVCAPS device type: %s\n", dbg_mcierr(err)); + if(!err) ok( parm.caps.dwReturn == MCI_DEVTYPE_CD_AUDIO, "getdevcaps device type: %u\n", parm.caps.dwReturn); + + err = mciSendCommandA(wDeviceID, MCI_RECORD, 0, (DWORD_PTR)&parm); + ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_RECORD: %s\n", dbg_mcierr(err)); + + /* Wine's MCI_MapMsgAtoW crashes on MCI_SAVE without parm->lpfilename */ + parm.save.lpfilename = "foo"; + err = mciSendCommandA(wDeviceID, MCI_SAVE, 0, (DWORD_PTR)&parm); + ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_SAVE: %s\n", dbg_mcierr(err)); + + /* commands from the core set are UNSUPPORTED, others UNRECOGNIZED */ + err = mciSendCommandA(wDeviceID, MCI_STEP, 0, (DWORD_PTR)&parm); + ok(err == MCIERR_UNRECOGNIZED_COMMAND, "MCI_STEP: %s\n", dbg_mcierr(err)); + + parm.status.dwItem = MCI_STATUS_TIME_FORMAT; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(!err, "STATUS time format: %s\n", dbg_mcierr(err)); + if(!err) ok(parm.status.dwReturn == MCI_FORMAT_MSF, "status time default format: %ld\n", parm.status.dwReturn); + + /* "CD-Audio" */ + err = mciSendStringA("info c product wait notify", buf, sizeof(buf), hwnd); + ok(!err, "info product: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "info notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + + parm.status.dwItem = MCI_STATUS_MEDIA_PRESENT; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(err || parm.status.dwReturn == TRUE || parm.status.dwReturn == FALSE, + "STATUS media present: %s\n", dbg_mcierr(err)); + + if (parm.status.dwReturn != TRUE) { + skip("No CD-ROM in drive.\n"); + return; + } + + parm.status.dwItem = MCI_STATUS_MODE; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(!err, "STATUS mode: %s\n", dbg_mcierr(err)); + switch(parm.status.dwReturn) { + case MCI_MODE_NOT_READY: + skip("CD-ROM mode not ready (DVD in drive?)\n"); + return; + case MCI_MODE_OPEN: /* should not happen with MEDIA_PRESENT */ + skip("CD-ROM drive is open\n"); + /* set door closed may not work. */ + return; + default: /* play/record/seek/pause */ + ok(parm.status.dwReturn==MCI_MODE_STOP, "STATUS mode is %lx\n", parm.status.dwReturn); + /* fall through */ + case MCI_MODE_STOP: /* normal */ + break; + } + + /* Initial mode is "stopped" with a CD in drive */ + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped"), "status mode is initially %s\n", buf); + + err = mciSendStringA("status c ready", buf, sizeof(buf), hwnd); + ok(!err, "status ready: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "true"), "status ready with media is %s\n", buf); + + err = mciSendStringA("info c product identity", buf, sizeof(buf), hwnd); + ok(!err, "info 2flags: %s\n", dbg_mcierr(err)); /* not MCIERR_FLAGS_NOT_COMPATIBLE */ + /* Precedence rule p>u>i verified experimentally, not tested here. */ + + err = mciSendStringA("info c identity", buf, sizeof(buf), hwnd); + ok(!err || err == MCIERR_HARDWARE, "info identity: %s\n", dbg_mcierr(err)); + /* a blank disk causes MCIERR_HARDWARE and other commands to fail likewise. */ + ok_hw = err; + + err = mciSendStringA("info c upc", buf, sizeof(buf), hwnd); + ok(err == ok_hw || err == MCIERR_NO_IDENTITY, "info upc: %s\n", dbg_mcierr(err)); + + parm.status.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; + parm.status.dwReturn = 0; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(err == ok_hw, "STATUS number of tracks: %s\n", dbg_mcierr(err)); + numtracks = parm.status.dwReturn; + /* cf. MAXIMUM_NUMBER_TRACKS */ + ok(0 < numtracks && numtracks <= 99, "number of tracks=%ld\n", parm.status.dwReturn); + + err = mciSendStringA("status c length", buf, sizeof(buf), hwnd); + ok(err == ok_hw, "status length: %s\n", dbg_mcierr(err)); + if(!err) trace("CD length %s\n", buf); + + if(err) { /* MCIERR_HARDWARE when given a blank disk */ + skip("status length %s (blank disk?)\n", dbg_mcierr(err)); + return; + } + + /* Linux leaves the drive at some random position, + * native initialises to the start position below. */ + err = mciSendStringA("status c position", buf, sizeof(buf), hwnd); + ok(!err, "status position: %s\n", dbg_mcierr(err)); + if(!err) todo_wine ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"), + "status position initially %s\n", buf); + /* 2 seconds is the initial position even with data tracks. */ + + err = mciSendStringA("status c position start notify", buf, sizeof(buf), hwnd); + ok(err == ok_hw, "status position start: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"), + "status position start %s\n", buf); + test_notification(hwnd, "status notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + + err = mciSendStringA("status c position start track 1 notify", buf, sizeof(buf), hwnd); + ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "status 2flags", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + + err = mciSendStringA("play c from 00:02:00 to 00:01:00 notify", buf, sizeof(buf), hwnd); + ok(err == MCIERR_OUTOFRANGE, "play 2s to 1s: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "play 2s to 1s", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + + err = mciSendStringA("resume c", buf, sizeof(buf), hwnd); + ok(err == MCIERR_HARDWARE || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION, + "resume without play: %s\n", dbg_mcierr(err)); /* not NONAPPLICABLE_FUNCTION */ + /* vmware with a .iso (data-only) yields no error on NT/w2k */ + + err = mciSendStringA("seek c wait", buf, sizeof(buf), hwnd); + ok(err == MCIERR_MISSING_PARAMETER, "seek noflag: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("seek c to start to end", buf, sizeof(buf), hwnd); + ok(err == MCIERR_FLAGS_NOT_COMPATIBLE || broken(!err), "seek to start+end: %s\n", dbg_mcierr(err)); + /* Win9x only errors out with Seek to start to */ + + /* set Wine to a defined position before play */ + err = mciSendStringA("seek c to start notify", buf, sizeof(buf), hwnd); + ok(!err, "seek to start: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "seek to start", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + /* Win9X Status position / current track then sometimes report the end position / track! */ + + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped"), "status mode after seek is %s\n", buf); + + /* MCICDA ignores MCI_SET_VIDEO */ + err = mciSendStringA("set c video on", buf, sizeof(buf), hwnd); + ok(!err, "set video: %s\n", dbg_mcierr(err)); + + /* One xp machine ignored SET_AUDIO, one w2k and one w7 machine honoured it + * and simultaneously toggled the mute button in the mixer control panel. + * Or does it only depend on the HW, not the OS? + * Some vmware machines return MCIERR_HARDWARE. */ + err = mciSendStringA("set c audio all on", buf, sizeof(buf), hwnd); + ok(!err || err == MCIERR_HARDWARE, "set audio: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("set c time format ms", buf, sizeof(buf), hwnd); + ok(!err, "set time format ms: %s\n", dbg_mcierr(err)); + + memset(buf, 0, sizeof(buf)); + err = mciSendStringA("status c position start", buf, sizeof(buf), hwnd); + ok(!err, "status position start (ms): %s\n", dbg_mcierr(err)); + duration = atoi(buf); + if(!err) ok(duration > 2000, "status position initially %sms\n", buf); + /* 00:02:00 corresponds to 2001 ms, 02:33 -> 2441 etc. */ + + err = mciSendStringA("status c position start track 1", buf, sizeof(buf), hwnd); + ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start+track: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status c notify wait", buf, sizeof(buf), hwnd); + ok(err == MCIERR_MISSING_PARAMETER, "status noflag: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status c length track 1", buf, sizeof(buf), hwnd); + ok(!err, "status length (ms): %s\n", dbg_mcierr(err)); + if(!err) { + trace("track #1 length %sms\n", buf); + duration = atoi(buf); + } else duration = 2001; /* for the position test below */ + + if (0) { /* causes some native systems to return Seek and Play with MCIERR_HARDWARE */ + /* depending on capability can eject only? */ + err = mciSendStringA("set c door closed notify", buf, sizeof(buf), hwnd); + ok(!err, "set door closed: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "door closed", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + } + /* Changing the disk while the MCI device is open causes the Status + * command to report stale data. Native obviously caches the TOC. */ + + /* status type track is localised, strcmp("audio|other") may fail. */ + parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK; + parm.status.dwTrack = 1; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm); + ok(!err, "STATUS type track 1: %s\n", dbg_mcierr(err)); + ok(parm.status.dwReturn==MCI_CDA_TRACK_OTHER || parm.status.dwReturn==MCI_CDA_TRACK_AUDIO, + "unknown track type %lx\n", parm.status.dwReturn); + + if (parm.status.dwReturn == MCI_CDA_TRACK_OTHER) { + /* Find an audio track */ + parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK; + parm.status.dwTrack = numtracks; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm); + ok(!err, "STATUS type track %u: %s\n", numtracks, dbg_mcierr(err)); + ok(parm.status.dwReturn == MCI_CDA_TRACK_OTHER || parm.status.dwReturn == MCI_CDA_TRACK_AUDIO, + "unknown track type %lx\n", parm.status.dwReturn); + track = (!err && parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) ? numtracks : 0; + + /* Seek to start (above) skips over data tracks + * In case of a data only CD, it seeks to the end of disk, however + * another Status position a few seconds later yields MCIERR_HARDWARE. */ + parm.status.dwItem = MCI_STATUS_POSITION; + parm.status.dwReturn = 2000; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm); + ok(!err || broken(err == MCIERR_HARDWARE), "STATUS position: %s\n", dbg_mcierr(err)); + + if(!err && track) ok(parm.status.dwReturn > duration, + "Seek did not skip data tracks, position %lums\n", parm.status.dwReturn); + /* dwReturn > start + length(#1) may fail because of small position report fluctuation. + * On some native systems, status position fluctuates around the target position; + * Successive calls return varying positions! */ + + err = mciSendStringA("set c time format msf", buf, sizeof(buf), hwnd); + ok(!err, "set time format msf: %s\n", dbg_mcierr(err)); + + parm.status.dwItem = MCI_STATUS_LENGTH; + parm.status.dwTrack = 1; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm); + ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err)); + duration = parm.status.dwReturn; + trace("track #1 length: %02um:%02us:%02uframes\n", + MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration)); + ok(duration>>24==0, "CD length high bits %08X\n", duration); + + /* TODO only with mixed CDs? */ + /* play track 1 to length silently works with data tracks */ + parm.play.dwFrom = MCI_MAKE_MSF(0,2,0); + parm.play.dwTo = duration; /* omitting 2 seconds from end */ + err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR)&parm); + ok(!err, "PLAY data to %08X: %s\n", duration, dbg_mcierr(err)); + + Sleep(1500*factor); /* Time to spin up, hopefully less than track length */ + + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped"), "status mode on data is %s\n", buf); + } else if (parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) { + skip("Got no mixed data+audio CD.\n"); + track = 1; + } else track = 0; + + if (!track) { + skip("Found no audio track.\n"); + return; + } + + err = mciSendStringA("set c time format msf", buf, sizeof(buf), hwnd); + ok(!err, "set time format msf: %s\n", dbg_mcierr(err)); + + parm.status.dwItem = MCI_STATUS_LENGTH; + parm.status.dwTrack = numtracks; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm); + ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err)); + duration = parm.status.dwReturn; + trace("last track length: %02um:%02us:%02uframes\n", + MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration)); + ok(duration>>24==0, "CD length high bits %08X\n", duration); + + parm.status.dwItem = MCI_STATUS_POSITION; + /* dwTrack is still set */ + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm); + ok(!err, "STATUS position start track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err)); + trace("last track position: %02um:%02us:%02uframes\n", + MCI_MSF_MINUTE(parm.status.dwReturn), MCI_MSF_SECOND(parm.status.dwReturn), MCI_MSF_FRAME(parm.status.dwReturn)); + + /* Seek to position + length always works, esp. + * for the last track it's NOT the position of the lead-out. */ + parm.seek.dwTo = MSF_Add(parm.status.dwReturn, duration); + err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm); + ok(!err, "SEEK to %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err)); + + parm.seek.dwTo = MSF_Add(parm.seek.dwTo, MCI_MAKE_MSF(0,0,1)); + err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm); + ok(err == MCIERR_OUTOFRANGE, "SEEK past %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err)); + + err = mciSendStringA("set c time format tmsf", buf, sizeof(buf), hwnd); + ok(!err, "set time format tmsf: %s\n", dbg_mcierr(err)); + + parm.play.dwFrom = track; + err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err)); + + if(err) { + skip("Cannot manage to play track %u.\n", track); + return; + } + + Sleep(1800*factor); /* Time to spin up, hopefully less than track length */ + + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "playing"), "status mode during play is %s\n", buf); + + err = mciSendStringA("pause c", buf, sizeof(buf), hwnd); + ok(!err, "pause: %s\n", dbg_mcierr(err)); + + test_notification(hwnd, "pause should abort notification", MCI_NOTIFY_ABORTED); + + /* Native returns stopped when paused, + * yet the Stop command is different as it would disallow Resume. */ + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) todo_wine ok(!strcmp(buf, "stopped"), "status mode while paused is %s\n", buf); + + err = mciSendCommandA(wDeviceID, MCI_RESUME, 0, 0); + ok(!err || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION, + "RESUME without parms: %s\n", dbg_mcierr(err)); + + Sleep(1300*factor); + + /* Native continues to play without interruption */ + err = mciSendCommandA(wDeviceID, MCI_PLAY, 0, 0); + todo_wine ok(!err, "PLAY without parms: %s\n", dbg_mcierr(err)); + + parm.play.dwFrom = MCI_MAKE_TMSF(numtracks,0,1,0); + parm.play.dwTo = 1; + err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR)&parm); + ok(err == MCIERR_OUTOFRANGE, "PLAY: %s\n", dbg_mcierr(err)); + + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "playing"), "status mode after play is %s\n", buf); + + err = mciSendCommandA(wDeviceID, MCI_STOP, MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err, "STOP notify: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "STOP notify", MCI_NOTIFY_SUCCESSFUL); + test_notification(hwnd, "STOP #1", 0); + + parm.play.dwFrom = track; + err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err)); + + Sleep(1600*factor); + + parm.seek.dwTo = 1; /* not , to test position below */ + err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm); + ok(!err, "SEEK to %u notify: %s\n", track, dbg_mcierr(err)); + /* Note that native's Status position / current track may move the head + * and reflect the new position only seconds after issuing the command. */ + + /* Seek stops */ + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped"), "status mode after play is %s\n", buf); + + test_notification(hwnd, "Seek aborts Play", MCI_NOTIFY_ABORTED); + test_notification(hwnd, "Seek", 0); + + parm.play.dwFrom = track; + parm.play.dwTo = MCI_MAKE_TMSF(track,0,0,21); /* 21 frames, subsecond */ + err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err)); + + Sleep(2200*factor); + + err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf); + if(!err && !strcmp(buf, "playing")) trace("status playing after sleep\n"); + + /* Playing to end asynchronously sends no notification! */ + test_notification(hwnd, "PLAY to end", 0); + + err = mciSendStringA("status c mode notify", buf, sizeof(buf), hwnd); + ok(!err, "status mode: %s\n", dbg_mcierr(err)); + if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf); + if(!err && !strcmp(buf, "playing")) trace("status still playing\n"); + /* Some systems report playing even after Sleep(3900ms) yet the successful + * notification tests (not ABORTED) indicates they are finished. */ + + test_notification(hwnd, "dangling from PLAY", MCI_NOTIFY_SUPERSEDED); + test_notification(hwnd, "status mode", MCI_NOTIFY_SUCCESSFUL); + + err = mciSendStringA("stop c", buf, sizeof(buf), hwnd); + ok(!err, "stop: %s\n", dbg_mcierr(err)); + + test_notification(hwnd, "PLAY to end", 0); + + /* length as MSF despite set time format TMSF */ + parm.status.dwItem = MCI_STATUS_LENGTH; + parm.status.dwTrack = numtracks; + parm.status.dwReturn = 0xFEEDABAD; + err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm); + ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err)); + ok(duration == parm.status.dwReturn, "length MSF<>TMSF %08lX\n", parm.status.dwReturn); + + /* Play from position start to start+length always works. */ + /* TODO? also play it using MSF */ + parm.play.dwFrom = numtracks; + parm.play.dwTo = (duration << 8) | numtracks; /* as TMSF */ + err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR)&parm); + ok(!err, "PLAY (TMSF) from %08X to %08X: %s\n", parm.play.dwFrom, parm.play.dwTo, dbg_mcierr(err)); + + Sleep(1400*factor); + + err = mciSendStringA("status c current track", buf, sizeof(buf), hwnd); + ok(!err, "status track: %s\n", dbg_mcierr(err)); + if(!err) todo_wine ok(numtracks == atoi(buf), "status current track gave %s, expected %u\n", buf, numtracks); + /* fails in Wine because SEEK is independent on IOCTL_CDROM_RAW_READ */ + + err = mciSendCommandA(wDeviceID, MCI_STOP, 0, 0); + ok(!err, "STOP: %s\n", dbg_mcierr(err)); + test_notification(hwnd, "STOP aborts", MCI_NOTIFY_ABORTED); + test_notification(hwnd, "STOP final", 0); +} + +static void test_openclose(HWND hwnd) +{ + MCIDEVICEID wDeviceID; + MCI_PARMS_UNION parm; + MCIERROR err; + char drive[] = {'a',':','\\','X','\0'}; + if (ok_open == MCIERR_CANNOT_LOAD_DRIVER) { + /* todo_wine Every open below should yield this same error. */ + skip("CD-ROM device likely not installed or disabled.\n"); + return; + } + + /* Bug in native since NT: After OPEN "c" without MCI_OPEN_ALIAS fails with + * MCIERR_DEVICE_OPEN, any subsequent OPEN fails with EXTENSION_NOT_FOUND! */ + parm.open.lpstrAlias = "x"; /* with alias, OPEN "c" behaves normally */ + parm.open.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO; + parm.open.lpstrElementName = drive; + for ( ; strlen(drive); drive[strlen(drive)-1] = 0) + for (drive[0] = 'a'; drive[0] <= 'z'; drive[0]++) { + err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | + MCI_OPEN_SHAREABLE | MCI_OPEN_ALIAS, (DWORD_PTR)&parm); + ok(!err || err == MCIERR_INVALID_FILE, "OPEN %s type: %s\n", drive, dbg_mcierr(err)); + /* open X:\ fails in Win9x/NT. Only open X: works everywhere. */ + if(!err) { + wDeviceID = parm.open.wDeviceID; + trace("ok with %s\n", drive); + err = mciSendCommandA(wDeviceID, MCI_CLOSE, 0, 0); + ok(!err,"mciCommand close returned %s\n", dbg_mcierr(err)); + } + } + drive[0] = '\\'; + err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | + MCI_OPEN_SHAREABLE, (DWORD_PTR)&parm); + ok(err == MCIERR_INVALID_FILE, "OPEN %s type: %s\n", drive, dbg_mcierr(err)); + if(!err) mciSendCommandA(parm.open.wDeviceID, MCI_CLOSE, 0, 0); + + if (0) { + parm.open.lpstrElementName = (LPCSTR)0xDEADBEEF; + err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID | + MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE, (DWORD_PTR)&parm); + todo_wine ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "OPEN elt_ID: %s\n", dbg_mcierr(err)); + if(!err) mciSendCommandA(parm.open.wDeviceID, MCI_CLOSE, 0, 0); + } +} + +START_TEST(mcicda) +{ + MCIERROR err; + HWND hwnd; + hwnd = CreateWindowExA(0, "static", "mcicda test", WS_POPUP, 0,0,100,100, + 0, 0, 0, NULL); + test_notification(hwnd, "-prior to tests-", 0); + test_play(hwnd); + test_openclose(hwnd); + err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_STOP, 0, 0); + todo_wine ok(!err || broken(err == MCIERR_HARDWARE /* blank CD or testbot without CD-ROM */), + "STOP all returned %s\n", dbg_mcierr(err)); + err = mciSendStringA("close all", NULL, 0, hwnd); + ok(!err, "final close all returned %s\n", dbg_mcierr(err)); + test_notification(hwnd, "-tests complete-", 0); + DestroyWindow(hwnd); +} diff --git a/rostests/winetests/winmm/midi.c b/rostests/winetests/winmm/midi.c new file mode 100644 index 00000000000..2f20e18e948 --- /dev/null +++ b/rostests/winetests/winmm/midi.c @@ -0,0 +1,848 @@ +/* + * Test winmm midi + * + * Copyright 2010 Jörg Höhle + * + * 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 + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#define _WINE + +#include +#include +#include "windows.h" +#include "mmsystem.h" +#include "wine/test.h" + +extern const char* mmsys_error(MMRESULT error); /* from wave.c */ + +/* Test in order of increasing probability to hang. + * On many UNIX systems, the Timidity softsynth provides + * MIDI sequencer services and it is not particularly robust. + */ + +#define MYCBINST 0x4CAFE5A8 /* not used with window or thread callbacks */ +#define WHATEVER 0xFEEDF00D + +static BOOL spurious_message(LPMSG msg) +{ + /* WM_DEVICECHANGE 0x0219 appears randomly */ + if(msg->message == WM_DEVICECHANGE) { + trace("skipping spurious message %04x\n", msg->message); + return TRUE; + } + return FALSE; +} + +static UINT cbmsg = 0; +static DWORD_PTR cbval1 = WHATEVER; +static DWORD_PTR cbval2 = 0; +static DWORD_PTR cbinst = MYCBINST; + +static void CALLBACK callback_func(HWAVEOUT hwo, UINT uMsg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2) +{ + if (winetest_debug>1) + trace("Callback! msg=%x %lx %lx\n", uMsg, dwParam1, dwParam2); + cbmsg = uMsg; + cbval1 = dwParam1; /* mhdr or 0 */ + cbval2 = dwParam2; /* always 0 */ + cbinst = dwInstance; /* MYCBINST, see midiOut/StreamOpen */ +} + +#define test_notification(hwnd, command, m1, p2) test_notification_dbg(hwnd, command, m1, p2, __LINE__) +static void test_notification_dbg(HWND hwnd, const char* command, UINT m1, DWORD_PTR p2, int line) +{ /* Use message type 0 as meaning no message */ + MSG msg; + if (hwnd) { + /* msg.wParam is hmidiout, msg.lParam is the mhdr (or 0) */ + BOOL seen; + do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); } + while(seen && spurious_message(&msg)); + if (m1 && !seen) { + /* We observe transient delayed notification, mostly on native. + * Perhaps the OS preempts the player thread after setting MHDR_DONE + * or clearing MHDR_INQUEUE, before calling DriverCallback. */ + DWORD rc; + trace_(__FILE__,line)("Waiting for delayed message %x from %s\n", m1, command); + SetLastError(0xDEADBEEF); + rc = MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE); + ok_(__FILE__,line)(rc==WAIT_OBJECT_0, "Wait failed: %04x %d\n", rc, GetLastError()); + seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); + } + if (seen) { + trace_(__FILE__,line)("Message %x, wParam=%lx, lParam=%lx from %s\n", + msg.message, msg.wParam, msg.lParam, command); + ok_(__FILE__,line)(msg.hwnd==hwnd, "Didn't get the handle to our test window\n"); + ok_(__FILE__,line)(msg.message==m1 && msg.lParam==p2, "bad message %x/%lx from %s, expect %x/%lx\n", msg.message, msg.lParam, command, m1, p2); + } + else ok_(__FILE__,line)(m1==0, "Expect message %x from %s\n", m1, command); + } + else { + /* FIXME: MOM_POSITIONCB and MOM_DONE are so close that a queue is needed. */ + if (cbmsg) { + ok_(__FILE__,line)(cbmsg==m1 && cbval1==p2 && cbval2==0, "bad callback %x/%lx/%lx from %s, expect %x/%lx\n", cbmsg, cbval1, cbval2, command, m1, p2); + cbmsg = 0; /* Mark as read */ + cbval1 = cbval2 = WHATEVER; + ok_(__FILE__,line)(cbinst==MYCBINST, "callback dwInstance changed to %lx\n", cbinst); + } + else ok_(__FILE__,line)(m1==0, "Expect callback %x from %s\n", m1, command); + } +} + + +static void test_midiIn_device(UINT udev, HWND hwnd) +{ + HMIDIIN hm; + MMRESULT rc; + MIDIINCAPSA capsA; + MIDIHDR mhdr; + + rc = midiInGetDevCapsA(udev, &capsA, sizeof(capsA)); + ok((MIDIMAPPER==udev) ? (rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/)) : rc==0, + "midiInGetDevCaps(dev=%d) rc=%s\n", udev, mmsys_error(rc)); + if (!rc) { + /* MIDI IN capsA.dwSupport may contain garbage, absent in old MS-Windows */ + trace("* %s: manufacturer=%d, product=%d, support=%X\n", capsA.szPname, capsA.wMid, capsA.wPid, capsA.dwSupport); + } + + if (hwnd) + rc = midiInOpen(&hm, udev, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW); + else + rc = midiInOpen(&hm, udev, (DWORD_PTR)callback_func, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION); + ok((MIDIMAPPER!=udev) ? rc==0 : (rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/)), + "midiInOpen(dev=%d) rc=%s\n", udev, mmsys_error(rc)); + if (rc) return; + + test_notification(hwnd, "midiInOpen", MIM_OPEN, 0); + + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.dwFlags = MHDR_DONE; + mhdr.dwUser = 0x56FA552C; + mhdr.dwBufferLength = 70000; /* > 64KB! */ + mhdr.dwBytesRecorded = 5; + mhdr.lpData = HeapAlloc(GetProcessHeap(), 0 , mhdr.dwBufferLength); + ok(mhdr.lpData!=NULL, "No %d bytes of memory!\n", mhdr.dwBufferLength); + if (mhdr.lpData) { + rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)-1); + ok(rc==MMSYSERR_INVALPARAM, "midiInPrepare tiny rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags |= MHDR_INQUEUE; + rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiInPrepare old size rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE|MHDR_DONE)/*w9x*/ || + mhdr.dwFlags == MHDR_PREPARED, "dwFlags=%x\n", mhdr.dwFlags); + trace("MIDIHDR flags=%x when unsent\n", mhdr.dwFlags); + + mhdr.dwFlags |= MHDR_INQUEUE|MHDR_DONE; + rc = midiInPrepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE|MHDR_DONE), "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags &= ~MHDR_INQUEUE; + rc = midiInUnprepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiInUnprepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags &= ~MHDR_DONE; + rc = midiInUnprepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiInUnprepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == 0, "dwFlags=%x\n", mhdr.dwFlags); + + rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == MHDR_PREPARED, "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags |= MHDR_DONE; + rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwBytesRecorded == 5, "BytesRec=%u\n", mhdr.dwBytesRecorded); + + mhdr.dwFlags |= MHDR_DONE; + rc = midiInAddBuffer(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiAddBuffer rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags); + + /* w95 does not set dwBytesRecorded=0 within midiInAddBuffer. Wine does. */ + if (mhdr.dwBytesRecorded != 0) + trace("dwBytesRecorded %u\n", mhdr.dwBytesRecorded); + + rc = midiInAddBuffer(hm, &mhdr, sizeof(mhdr)); + ok(rc==MIDIERR_STILLPLAYING, "midiAddBuffer rc=%s\n", mmsys_error(rc)); + + rc = midiInPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiInPrepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags); + } + rc = midiInReset(hm); /* Return any pending buffer */ + ok(!rc, "midiInReset rc=%s\n", mmsys_error(rc)); + test_notification(hwnd, "midiInReset", MIM_LONGDATA, (DWORD_PTR)&mhdr); + + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE), "dwFlags=%x\n", mhdr.dwFlags); + rc = midiInUnprepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiInUnprepare rc=%s\n", mmsys_error(rc)); + + ok(mhdr.dwBytesRecorded == 0, "Did some MIDI HW send %u bytes?\n", mhdr.dwBytesRecorded); + + rc = midiInClose(hm); + ok(!rc, "midiInClose rc=%s\n", mmsys_error(rc)); + + ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser); + HeapFree(GetProcessHeap(), 0, mhdr.lpData); + test_notification(hwnd, "midiInClose", MIM_CLOSE, 0); + test_notification(hwnd, "midiIn over", 0, WHATEVER); +} + +static void test_midi_infns(HWND hwnd) +{ + HMIDIIN hm; + MMRESULT rc; + UINT udev, ndevs = midiInGetNumDevs(); + + rc = midiInOpen(&hm, ndevs, 0, 0, CALLBACK_NULL); + ok(rc==MMSYSERR_BADDEVICEID, "midiInOpen udev>max rc=%s\n", mmsys_error(rc)); + if (!rc) { + rc = midiInClose(hm); + ok(!rc, "midiInClose rc=%s\n", mmsys_error(rc)); + } + if (!ndevs) { + trace("Found no MIDI IN device\n"); /* no skip for this common situation */ + rc = midiInOpen(&hm, MIDIMAPPER, 0, 0, CALLBACK_NULL); + ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/), "midiInOpen MAPPER with no MIDI rc=%s\n", mmsys_error(rc)); + if (!rc) { + rc = midiInClose(hm); + ok(!rc, "midiInClose rc=%s\n", mmsys_error(rc)); + } + return; + } + trace("Found %d MIDI IN devices\n", ndevs); + for (udev=0; udev < ndevs; udev++) { + trace("** Testing device %d\n", udev); + test_midiIn_device(udev, hwnd); + Sleep(50); + } + trace("** Testing MIDI mapper\n"); + test_midiIn_device(MIDIMAPPER, hwnd); +} + + +static void test_midi_mci(HWND hwnd) +{ + MCIERROR err; + char buf[1024]; + memset(buf, 0, sizeof(buf)); + + err = mciSendStringA("sysinfo sequencer quantity", buf, sizeof(buf), hwnd); + ok(!err, "mci sysinfo sequencer quantity returned %d\n", err); + if (!err) trace("Found %s MCI sequencer devices\n", buf); + + if (!strcmp(buf, "0")) return; + + err = mciSendStringA("capability sequencer can record", buf, sizeof(buf), hwnd); + ok(!err, "mci sysinfo sequencer quantity returned %d\n", err); + if(!err) ok(!strcmp(buf, "false"), "capability can record is %s\n", buf); +} + + +static void test_midiOut_device(UINT udev, HWND hwnd) +{ + HMIDIOUT hm; + MMRESULT rc; + MIDIOUTCAPSA capsA; + DWORD ovolume; + UINT udevid; + MIDIHDR mhdr; + + rc = midiOutGetDevCapsA(udev, &capsA, sizeof(capsA)); + ok(!rc, "midiOutGetDevCaps(dev=%d) rc=%s\n", udev, mmsys_error(rc)); + if (!rc) { + trace("* %s: manufacturer=%d, product=%d, tech=%d, support=%X: %d voices, %d notes\n", + capsA.szPname, capsA.wMid, capsA.wPid, capsA.wTechnology, capsA.dwSupport, capsA.wVoices, capsA.wNotes); + ok(!((MIDIMAPPER==udev) ^ (MOD_MAPPER==capsA.wTechnology)), "technology %d on device %d\n", capsA.wTechnology, udev); + if (MOD_MIDIPORT == capsA.wTechnology) { + ok(capsA.wVoices == 0 && capsA.wNotes == 0, "external device with notes or voices\n"); + ok(capsA.wChannelMask == 0xFFFF, "external device channel mask %x\n", capsA.wChannelMask); + ok(!(capsA.dwSupport & (MIDICAPS_VOLUME|MIDICAPS_LRVOLUME|MIDICAPS_CACHE)), "external device support=%X\n", capsA.dwSupport); + } + } + + if (hwnd) + rc = midiOutOpen(&hm, udev, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW); + else + rc = midiOutOpen(&hm, udev, (DWORD_PTR)callback_func, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION); + if (rc == MMSYSERR_NOTSUPPORTED || rc == MMSYSERR_NODRIVER) + { + skip( "MIDI out not supported\n" ); + return; + } + ok(!rc, "midiOutOpen(dev=%d) rc=%s\n", udev, mmsys_error(rc)); + if (rc) return; + + test_notification(hwnd, "midiOutOpen", MOM_OPEN, 0); + + rc = midiOutGetVolume(hm, &ovolume); + ok((capsA.dwSupport & MIDICAPS_VOLUME) ? rc==MMSYSERR_NOERROR : rc==MMSYSERR_NOTSUPPORTED, "midiOutGetVolume rc=%s\n", mmsys_error(rc)); + /* The native mapper responds with FFFFFFFF initially, + * real devices with the volume GUI SW-synth settings. */ + if (!rc) trace("Current volume %x on device %d\n", ovolume, udev); + + /* The W95 ESFM Synthesis device reports NOTENABLED although + * GetVolume by handle works and music plays. */ + rc = midiOutGetVolume(UlongToHandle(udev), &ovolume); + ok((capsA.dwSupport & MIDICAPS_VOLUME) ? rc==MMSYSERR_NOERROR || broken(rc==MMSYSERR_NOTENABLED) : rc==MMSYSERR_NOTSUPPORTED, "midiOutGetVolume(dev=%d) rc=%s\n", udev, mmsys_error(rc)); + + rc = midiOutGetVolume(hm, NULL); + ok(rc==MMSYSERR_INVALPARAM, "midiOutGetVolume NULL rc=%s\n", mmsys_error(rc)); + + /* Tests with midiOutSetvolume show that the midi mapper forwards + * the value to the real device, but Get initially always reports + * FFFFFFFF. Therefore, a Get+SetVolume pair with the mapper is + * not adequate to restore the value prior to tests. + */ + if (winetest_interactive && (capsA.dwSupport & MIDICAPS_VOLUME)) { + DWORD volume2 = (ovolume < 0x80000000) ? 0xC000C000 : 0x40004000; + rc = midiOutSetVolume(hm, volume2); + ok(!rc, "midiOutSetVolume rc=%s\n", mmsys_error(rc)); + if (!rc) { + DWORD volume3; + rc = midiOutGetVolume(hm, &volume3); + ok(!rc, "midiOutGetVolume new rc=%s\n", mmsys_error(rc)); + if (!rc) trace("New volume %x on device %d\n", volume3, udev); + todo_wine ok(volume2==volume3, "volume Set %x = Get %x\n", volume2, volume3); + + rc = midiOutSetVolume(hm, ovolume); + ok(!rc, "midiOutSetVolume restore rc=%s\n", mmsys_error(rc)); + } + } + rc = midiOutGetDevCapsA((UINT_PTR)hm, &capsA, sizeof(capsA)); + ok(!rc, "midiOutGetDevCaps(dev=%d) by handle rc=%s\n", udev, mmsys_error(rc)); + rc = midiInGetDevCapsA((UINT_PTR)hm, (LPMIDIINCAPSA)&capsA, sizeof(DWORD)); + ok(rc==MMSYSERR_BADDEVICEID, "midiInGetDevCaps(dev=%d) by out handle rc=%s\n", udev, mmsys_error(rc)); + + { DWORD e = 0x006F4893; /* velocity, note (#69 would be 440Hz) channel */ + trace("ShortMsg type %x\n", LOBYTE(LOWORD(e))); + rc = midiOutShortMsg(hm, e); + ok(!rc, "midiOutShortMsg rc=%s\n", mmsys_error(rc)); + if (!rc) Sleep(400); /* Hear note */ + } + + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.dwFlags = MHDR_DONE; + mhdr.dwUser = 0x56FA552C; + mhdr.dwOffset = 0xDEADBEEF; + mhdr.dwBufferLength = 70000; /* > 64KB! */ + mhdr.lpData = HeapAlloc(GetProcessHeap(), 0 , mhdr.dwBufferLength); + ok(mhdr.lpData!=NULL, "No %d bytes of memory!\n", mhdr.dwBufferLength); + if (mhdr.lpData) { + rc = midiOutLongMsg(hm, &mhdr, sizeof(mhdr)); + ok(rc==MIDIERR_UNPREPARED, "midiOutLongMsg unprepared rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags); + test_notification(hwnd, "midiOutLong unprepared", 0, WHATEVER); + + rc = midiOutPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)-1); + ok(rc==MMSYSERR_INVALPARAM, "midiOutPrepare tiny rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags); + + /* Since at least w2k, midiOutPrepare clears the DONE and INQUEUE flags. w95 didn't. */ + /* mhdr.dwFlags |= MHDR_INQUEUE; would cause w95 to return STILLPLAYING from Unprepare */ + rc = midiOutPrepareHeader(hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiOutPrepare old size rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE)/*w9x*/ || + mhdr.dwFlags == MHDR_PREPARED, "dwFlags=%x\n", mhdr.dwFlags); + trace("MIDIHDR flags=%x when unsent\n", mhdr.dwFlags); + + /* No flag is cleared when already prepared. */ + mhdr.dwFlags |= MHDR_DONE|MHDR_INQUEUE; + rc = midiOutPrepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiOutPrepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags |= MHDR_INQUEUE; + rc = midiOutUnprepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(rc==MIDIERR_STILLPLAYING, "midiOutUnprepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_PREPARED|MHDR_DONE|MHDR_INQUEUE), "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags &= ~MHDR_INQUEUE; + rc = midiOutUnprepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == MHDR_DONE, "dwFlags=%x\n", mhdr.dwFlags); + + mhdr.dwFlags |= MHDR_INQUEUE; + rc = midiOutUnprepareHeader(hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags == (MHDR_INQUEUE|MHDR_DONE), "dwFlags=%x\n", mhdr.dwFlags); + + HeapFree(GetProcessHeap(), 0, mhdr.lpData); + } + ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser); + ok(mhdr.dwOffset==0xDEADBEEF, "MIDIHDR.dwOffset changed to %x\n", mhdr.dwOffset); + + rc = midiOutGetID(hm, &udevid); + ok(!rc, "midiOutGetID rc=%s\n", mmsys_error(rc)); + if(!rc) ok(udevid==udev, "midiOutGetID gives %d, expect %d\n", udevid, udev); + + rc = midiOutReset(hm); /* Quiet everything */ + ok(!rc, "midiOutReset rc=%s\n", mmsys_error(rc)); + + rc = midiOutClose(hm); + ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc)); + test_notification(hwnd, "midiOutClose", MOM_CLOSE, 0); + + rc = midiOutOpen(&hm, udev, 0, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW); + /* w95 broken(rc==MMSYSERR_INVALPARAM) see WINMM_CheckCallback */ + ok(!rc, "midiOutOpen(dev=%d) 0 CALLBACK_WINDOW rc=%s\n", udev, mmsys_error(rc)); + /* PostMessage(hwnd=0) redirects to PostThreadMessage(GetCurrentThreadId()) + * which PeekMessage((HWND)-1) queries. */ + test_notification((HWND)-1, "midiOutOpen WINDOW->THREAD", 0, WHATEVER); + test_notification(hwnd, "midiOutOpen WINDOW", 0, WHATEVER); + if (!rc) { + rc = midiOutClose(hm); + ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc)); + test_notification((HWND)-1, "midiOutClose WINDOW->THREAD", 0, WHATEVER); + test_notification(hwnd, "midiOutClose", 0, WHATEVER); + } + test_notification(hwnd, "midiOut over", 0, WHATEVER); +} + +static void test_position(HMIDISTRM hm, UINT typein, UINT typeout) +{ + MMRESULT rc; + MMTIME mmtime; + mmtime.wType = typein; + rc = midiStreamPosition(hm, &mmtime, sizeof(MMTIME)); + /* Ugly, but a single ok() herein enables using the todo_wine prefix */ + ok(!rc && (mmtime.wType == typeout), "midiStreamPosition type %x converted to %x rc=%s\n", typein, mmtime.wType, mmsys_error(rc)); + if (!rc) switch(mmtime.wType) { + case TIME_MS: + trace("Stream position %ums\n", mmtime.u.ms); + break; + case TIME_TICKS: + trace("Stream position %u ticks\n", mmtime.u.ticks); + break; + case TIME_MIDI: + trace("Stream position song pointer %u\n", mmtime.u.midi.songptrpos); + break; + } +} + +typedef struct midishortevent_tag { /* ideal size for MEVT_F_SHORT event type */ + DWORD dwDeltaTime; + DWORD dwStreamID; + DWORD dwEvent; +} MIDISHORTEVENT; + +/* Native crashes on a second run with the const qualifier set on this data! */ +static BYTE strmEvents[] = { /* A set of variable-sized MIDIEVENT structs */ + 0, 0, 0, 0, 0, 0, 0, 0, /* dwDeltaTime and dwStreamID */ + 0, 0, 0, MEVT_NOP | 0x40, /* with MEVT_F_CALLBACK */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0xE0, 0x93, 0x04, MEVT_TEMPO, /* 0493E0 == 300000 */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0x93, 0x48, 0x6F, MEVT_SHORTMSG, +}; + +static MIDISHORTEVENT strmNops[] = { /* Test callback + dwOffset */ + { 0, 0, (MEVT_NOP <<24)| MEVT_F_CALLBACK }, + { 0, 0, (MEVT_NOP <<24)| MEVT_F_CALLBACK }, +}; + +static MMRESULT playStream(HMIDISTRM hm, LPMIDIHDR lpMidiHdr) +{ + MMRESULT rc = midiStreamOut(hm, lpMidiHdr, sizeof(MIDIHDR)); + /* virtual machines may return MIDIERR_STILLPLAYING from the next request + * even after MHDR_DONE is set. It's still too early, so add MHDR_INQUEUE. */ + if (!rc) while (!(lpMidiHdr->dwFlags & MHDR_DONE) || (lpMidiHdr->dwFlags & MHDR_INQUEUE)) { Sleep(100); } + return rc; +} + +static void test_midiStream(UINT udev, HWND hwnd) +{ + HMIDISTRM hm; + MMRESULT rc, rc2; + MIDIHDR mhdr; + union { + MIDIPROPTEMPO tempo; + MIDIPROPTIMEDIV tdiv; + } midiprop; + + if (hwnd) + rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)hwnd, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW); + else + rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)callback_func, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION); + if (rc == MMSYSERR_NOTSUPPORTED || rc == MMSYSERR_NODRIVER) + { + skip( "MIDI stream not supported\n" ); + return; + } + ok(!rc, "midiStreamOpen(dev=%d) rc=%s\n", udev, mmsys_error(rc)); + if (rc) return; + + test_notification(hwnd, "midiStreamOpen", MOM_OPEN, 0); + + midiprop.tempo.cbStruct = sizeof(midiprop.tempo); + rc = midiStreamProperty(hm, (void*)&midiprop, MIDIPROP_GET|MIDIPROP_TEMPO); + ok(!rc, "midiStreamProperty TEMPO rc=%s\n", mmsys_error(rc)); + ok(midiprop.tempo.dwTempo==500000, "default stream tempo %u microsec per quarter note\n", midiprop.tempo.dwTempo); + + midiprop.tdiv.cbStruct = sizeof(midiprop.tdiv); + rc = midiStreamProperty(hm, (void*)&midiprop, MIDIPROP_GET|MIDIPROP_TIMEDIV); + ok(!rc, "midiStreamProperty TIMEDIV rc=%s\n", mmsys_error(rc)); + todo_wine ok(24==LOWORD(midiprop.tdiv.dwTimeDiv), "default stream time division %u\n", midiprop.tdiv.dwTimeDiv); + + memset(&mhdr, 0, sizeof(mhdr)); + mhdr.dwUser = 0x56FA552C; + mhdr.dwOffset = 1234567890; + mhdr.dwBufferLength = sizeof(strmEvents); + mhdr.dwBytesRecorded = mhdr.dwBufferLength; + mhdr.lpData = (LPSTR)&strmEvents[0]; + if (mhdr.lpData) { + rc = midiOutLongMsg((HMIDIOUT)hm, &mhdr, sizeof(mhdr)); + ok(rc==MIDIERR_UNPREPARED, "midiOutLongMsg unprepared rc=%s\n", mmsys_error(rc)); + test_notification(hwnd, "midiOutLong unprepared", 0, WHATEVER); + + rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)-1); + ok(rc==MMSYSERR_INVALPARAM, "midiOutPrepare tiny rc=%s\n", mmsys_error(rc)); + rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiOutPrepare old size rc=%s\n", mmsys_error(rc)); + ok(mhdr.dwFlags & MHDR_PREPARED, "MHDR.dwFlags when prepared %x\n", mhdr.dwFlags); + + /* The device is still in paused mode and should queue the message. */ + rc = midiStreamOut(hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiStreamOut old size rc=%s\n", mmsys_error(rc)); + rc2 = rc; + trace("MIDIHDR flags=%x when submitted\n", mhdr.dwFlags); + /* w9X/me does not set MHDR_ISSTRM when StreamOut exits, + * but it will be set on all systems after the job is finished. */ + + Sleep(90); + /* Wine <1.1.39 started playing immediately */ + test_notification(hwnd, "midiStream still paused", 0, WHATEVER); + + /* MSDN asks to use midiStreamRestart prior to midiStreamOut() + * because the starting state is 'pause', but some apps seem to + * work with the inverse order: queue everything, then play. + */ + + rc = midiStreamRestart(hm); + ok(!rc, "midiStreamRestart rc=%s\n", mmsys_error(rc)); + + if (!rc2) while(mhdr.dwFlags & MHDR_INQUEUE) { + trace("async MIDI still queued\n"); + Sleep(100); + } /* Checking INQUEUE is not the recommended way to wait for the end of a job, but we're testing. */ + /* MHDR_ISSTRM is not necessarily set when midiStreamOut returns + * rather than when the queue is eventually processed. */ + ok(mhdr.dwFlags & MHDR_ISSTRM, "MHDR.dwFlags %x no ISSTRM when out of queue\n", mhdr.dwFlags); + if (!rc2) while(!(mhdr.dwFlags & MHDR_DONE)) { + /* Never to be seen except perhaps on multicore */ + trace("async MIDI still not done\n"); + Sleep(100); + } + ok(mhdr.dwFlags & MHDR_DONE, "MHDR.dwFlags %x not DONE when out of queue\n", mhdr.dwFlags); + test_notification(hwnd, "midiStream callback", MOM_POSITIONCB, (DWORD_PTR)&mhdr); + test_notification(hwnd, "midiStreamOut", MOM_DONE, (DWORD_PTR)&mhdr); + + /* Native fills dwOffset regardless of the cbMidiHdr size argument to midiStreamOut */ + ok(1234567890!=mhdr.dwOffset, "play left MIDIHDR.dwOffset at %u\n", mhdr.dwOffset); + + rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc)); + rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiOutUnprepare #2 rc=%s\n", mmsys_error(rc)); + + trace("MIDIHDR stream flags=%x when finished\n", mhdr.dwFlags); + ok(mhdr.dwFlags & MHDR_DONE, "MHDR.dwFlags when done %x\n", mhdr.dwFlags); + + test_position(hm, TIME_MS, TIME_MS); + test_position(hm, TIME_TICKS, TIME_TICKS); + todo_wine test_position(hm, TIME_MIDI, TIME_MIDI); + test_position(hm, TIME_SMPTE, TIME_MS); + test_position(hm, TIME_SAMPLES, TIME_MS); + test_position(hm, TIME_BYTES, TIME_MS); + + Sleep(400); /* Hear note */ + + midiprop.tempo.cbStruct = sizeof(midiprop.tempo); + rc = midiStreamProperty(hm, (void*)&midiprop, MIDIPROP_GET|MIDIPROP_TEMPO); + ok(!rc, "midiStreamProperty TEMPO rc=%s\n", mmsys_error(rc)); + ok(0x0493E0==midiprop.tempo.dwTempo, "stream set tempo %u\n", midiprop.tdiv.dwTimeDiv); + + rc = midiStreamRestart(hm); + ok(!rc, "midiStreamRestart #2 rc=%s\n", mmsys_error(rc)); + + mhdr.dwFlags |= MHDR_ISSTRM; + /* Preset flags (e.g. MHDR_ISSTRM) do not disturb. */ + rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiOutPrepare used flags %x rc=%s\n", mhdr.dwFlags, mmsys_error(rc)); + rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, offsetof(MIDIHDR,dwOffset)); + ok(!rc, "midiOutUnprepare used flags %x rc=%s\n", mhdr.dwFlags, mmsys_error(rc)); + + rc = midiStreamRestart(hm); + ok(!rc, "midiStreamRestart #3 rc=%s\n", mmsys_error(rc)); + } + ok(mhdr.dwUser==0x56FA552C, "MIDIHDR.dwUser changed to %lx\n", mhdr.dwUser); + ok(0==((MIDISHORTEVENT*)&strmEvents)[0].dwStreamID, "dwStreamID set to %x\n", ((LPMIDIEVENT)&strmEvents[0])->dwStreamID); + + /* dwBytesRecorded controls how much is played, not dwBufferLength + * allowing to immediately forward packets from midiIn to midiOut */ + mhdr.dwOffset = 1234123123; + mhdr.dwBufferLength = sizeof(strmNops); + trace("buffer: %u\n", mhdr.dwBufferLength); + mhdr.dwBytesRecorded = 0; + mhdr.lpData = (LPSTR)&strmNops[0]; + strmNops[0].dwEvent |= MEVT_F_CALLBACK; + strmNops[1].dwEvent |= MEVT_F_CALLBACK; + + rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiOutPrepare rc=%s\n", mmsys_error(rc)); + + rc = playStream(hm, &mhdr); + ok(!rc, "midiStreamOut 0 bytes recorded rc=%s\n", mmsys_error(rc)); + + test_notification(hwnd, "midiStreamOut", MOM_DONE, (DWORD_PTR)&mhdr); + test_notification(hwnd, "0 bytes recorded", 0, WHATEVER); + + /* FIXME: check dwOffset within callback + * instead of the unspecified value afterwards */ + ok(1234123123==mhdr.dwOffset || broken(0==mhdr.dwOffset), "play 0 set MIDIHDR.dwOffset to %u\n", mhdr.dwOffset); + /* w2k and later only set dwOffset when processing MEVT_T_CALLBACK, + * while w9X/me/nt always sets it. Have Wine behave like w2k because the + * dwOffset slot does not exist in the small size MIDIHDR. */ + + mhdr.dwOffset = 1234123123; + mhdr.dwBytesRecorded = 1*sizeof(MIDISHORTEVENT); + + rc = playStream(hm, &mhdr); + ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc)); + + test_notification(hwnd, "1 of 2 events", MOM_POSITIONCB, (DWORD_PTR)&mhdr); + test_notification(hwnd, "1 of 2 events", MOM_DONE, (DWORD_PTR)&mhdr); + test_notification(hwnd, "1 of 2 events", 0, WHATEVER); + ok(0==mhdr.dwOffset, "MIDIHDR.dwOffset 1/2 changed to %u\n", mhdr.dwOffset); + + mhdr.dwOffset = 1234123123; + mhdr.dwBytesRecorded = 2*sizeof(MIDISHORTEVENT); + + rc = playStream(hm, &mhdr); + ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc)); + + test_notification(hwnd, "2 of 2 events", MOM_POSITIONCB, (DWORD_PTR)&mhdr); + test_notification(hwnd, "2 of 2 events", MOM_POSITIONCB, (DWORD_PTR)&mhdr); + test_notification(hwnd, "2 of 2 events", MOM_DONE, (DWORD_PTR)&mhdr); + test_notification(hwnd, "2 of 2 events", 0, WHATEVER); + ok(sizeof(MIDISHORTEVENT)==mhdr.dwOffset, "MIDIHDR.dwOffset 2/2 changed to %u\n", mhdr.dwOffset); + ok(mhdr.dwBytesRecorded == 2*sizeof(MIDISHORTEVENT), "dwBytesRecorded changed to %u\n", mhdr.dwBytesRecorded); + + strmNops[0].dwEvent &= ~MEVT_F_CALLBACK; + strmNops[1].dwEvent &= ~MEVT_F_CALLBACK; + mhdr.dwOffset = 1234123123; + rc = playStream(hm, &mhdr); + ok(!rc, "midiStreamOut 1 event out of 2 rc=%s\n", mmsys_error(rc)); + + test_notification(hwnd, "0 CB in 2 events", MOM_DONE, (DWORD_PTR)&mhdr); + test_notification(hwnd, "0 CB in 2 events", 0, WHATEVER); + /* w9X/me/nt set dwOffset to the position played last */ + ok(1234123123==mhdr.dwOffset || broken(sizeof(MIDISHORTEVENT)==mhdr.dwOffset), "MIDIHDR.dwOffset nocb changed to %u\n", mhdr.dwOffset); + + mhdr.dwBytesRecorded = mhdr.dwBufferLength-1; + rc = playStream(hm, &mhdr); + ok(rc==MMSYSERR_INVALPARAM,"midiStreamOut dwBytesRecorded modulo MIDIEVENT rc=%s\n", mmsys_error(rc)); + if (!rc) { + test_notification(hwnd, "2 of 2 events", MOM_DONE, (DWORD_PTR)&mhdr); + } + + mhdr.dwBytesRecorded = mhdr.dwBufferLength+1; + rc = playStream(hm, &mhdr); + ok(rc==MMSYSERR_INVALPARAM,"midiStreamOut dwBufferLength 64KB! */ + mhdr.lpData = HeapAlloc(GetProcessHeap(), 0 , mhdr.dwBufferLength); + ok(mhdr.lpData!=NULL, "No %d bytes of memory!\n", mhdr.dwBufferLength); + if (mhdr.lpData) { + mhdr.dwFlags = 0; + /* PrepareHeader detects the too large buffer is for a stream. */ + rc = midiOutPrepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr)); + todo_wine ok(rc==MMSYSERR_INVALPARAM, "midiOutPrepare stream too large rc=%s\n", mmsys_error(rc)); + + rc = midiOutUnprepareHeader((HMIDIOUT)hm, &mhdr, sizeof(mhdr)); + ok(!rc, "midiOutUnprepare rc=%s\n", mmsys_error(rc)); + + HeapFree(GetProcessHeap(), 0, mhdr.lpData); + } + + rc = midiStreamClose(hm); + ok(!rc, "midiStreamClose rc=%s\n", mmsys_error(rc)); + test_notification(hwnd, "midiStreamClose", MOM_CLOSE, 0); + test_notification(hwnd, "midiStream over", 0, WHATEVER); + + rc = midiStreamOpen(&hm, &udev, 1, 0, (DWORD_PTR)MYCBINST, CALLBACK_FUNCTION); + ok(!rc /*w2k*/|| rc==MMSYSERR_INVALPARAM/*w98*/, "midiStreamOpen NULL function rc=%s\n", mmsys_error(rc)); + if (!rc) { + trace("Device %d accepts NULL CALLBACK_FUNCTION\n", udev); + rc = midiStreamClose(hm); + ok(!rc, "midiStreamClose rc=%s\n", mmsys_error(rc)); + } + + rc = midiStreamOpen(&hm, &udev, 1, (DWORD_PTR)0xDEADBEEF, (DWORD_PTR)MYCBINST, CALLBACK_WINDOW); + ok(rc==MMSYSERR_INVALPARAM, "midiStreamOpen bad window rc=%s\n", mmsys_error(rc)); + if (!rc) { + rc = midiStreamClose(hm); + ok(!rc, "midiStreamClose rc=%s\n", mmsys_error(rc)); + } +} + +static BOOL scan_subkeys(HKEY parent, const LPCSTR *sub_keys) +{ + char name[64]; + DWORD index = 0; + DWORD name_len = sizeof(name); + BOOL found_vmware = FALSE; + + if (sub_keys[0] == NULL) + { + /* We're at the deepest level, check "Identifier" value now */ + char *test; + if (RegQueryValueExA(parent, "Identifier", NULL, NULL, (LPBYTE) name, &name_len) != ERROR_SUCCESS) + return FALSE; + for (test = name; test < name + lstrlenA(name) - 6 && ! found_vmware; test++) + { + char c = test[6]; + test[6] = '\0'; + found_vmware = (lstrcmpiA(test, "VMware") == 0); + test[6] = c; + } + return found_vmware; + } + + while (RegEnumKeyExA(parent, index, name, &name_len, NULL, NULL, NULL, NULL) == ERROR_SUCCESS && + ! found_vmware) { + char c = name[lstrlenA(sub_keys[0])]; + name[lstrlenA(sub_keys[0])] = '\0'; + if (lstrcmpiA(name, sub_keys[0]) == 0) { + HKEY sub_key; + name[lstrlenA(sub_keys[0])] = c; + if (RegOpenKeyExA(parent, name, 0, KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &sub_key) == ERROR_SUCCESS) { + found_vmware = scan_subkeys(sub_key, sub_keys + 1); + RegCloseKey(sub_key); + } + } + + name_len = sizeof(name); + index++; + } + + return found_vmware; +} + +/* + * Usual method to detect whether running inside a VMware virtual machine involves direct port I/O requiring + * some assembly and an exception handler. Can't do that in Wine tests. Alternative method of querying WMI + * is not available on NT4. So instead we look at the device map and check the Identifier value in the + * registry keys HKLM\HARDWARE\DEVICEMAP\SCSI\Scsi Port x\Scsi Bus x\Target Id x\Logical Unit Id x (where + * x is some number). If the Identifier value contains the string "VMware" we assume running in a VMware VM. + */ +static BOOL on_vmware(void) +{ + static const LPCSTR sub_keys[] = { "Scsi Port ", "Scsi Bus ", "Target Id ", "Logical Unit Id ", NULL }; + HKEY scsi; + BOOL found_vmware = FALSE; + + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DEVICEMAP\\Scsi", 0, KEY_ENUMERATE_SUB_KEYS, &scsi) != ERROR_SUCCESS) + return FALSE; + + found_vmware = scan_subkeys(scsi, sub_keys); + + RegCloseKey(scsi); + + return found_vmware; +} + +static void test_midi_outfns(HWND hwnd) +{ + HMIDIOUT hm; + MMRESULT rc; + UINT udev, ndevs = midiOutGetNumDevs(); + + rc = midiOutOpen(&hm, ndevs, 0, 0, CALLBACK_NULL); + ok(rc==MMSYSERR_BADDEVICEID, "midiOutOpen udev>max rc=%s\n", mmsys_error(rc)); + if (!rc) { + rc = midiOutClose(hm); + ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc)); + } + if (!ndevs) { + MIDIOUTCAPSA capsA; + skip("Found no MIDI out device\n"); + + rc = midiOutGetDevCapsA(MIDIMAPPER, &capsA, sizeof(capsA)); + /* GetDevCaps and Open must return compatible results */ + ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*nt,w2k*/), "midiOutGetDevCaps MAPPER with no MIDI rc=%s\n", mmsys_error(rc)); + + rc = midiOutOpen(&hm, MIDIMAPPER, 0, 0, CALLBACK_NULL); + if (rc==MIDIERR_INVALIDSETUP) todo_wine /* Wine without snd-seq */ + ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*w2k*/), "midiOutOpen MAPPER with no MIDI rc=%s\n", mmsys_error(rc)); + else + ok(rc==MMSYSERR_BADDEVICEID || broken(rc==MMSYSERR_NODRIVER /*w2k sound disabled*/), + "midiOutOpen MAPPER with no MIDI rc=%s\n", mmsys_error(rc)); + if (!rc) { + rc = midiOutClose(hm); + ok(!rc, "midiOutClose rc=%s\n", mmsys_error(rc)); + } + return; + } + trace("Found %d MIDI OUT devices\n", ndevs); + + test_midi_mci(hwnd); + + for (udev=0; udev < ndevs; udev++) { + MIDIOUTCAPSA capsA; + rc = midiOutGetDevCapsA(udev, &capsA, sizeof(capsA)); + if (rc || strcmp(capsA.szPname, "Creative Sound Blaster MPU-401") != 0 || ! on_vmware()) { + trace("** Testing device %d\n", udev); + test_midiOut_device(udev, hwnd); + Sleep(800); /* Let the synth rest */ + test_midiStream(udev, hwnd); + Sleep(800); + } + else + win_skip("Skipping this device on VMware, driver problem\n"); + } + trace("** Testing MIDI mapper\n"); + test_midiOut_device(MIDIMAPPER, hwnd); + Sleep(800); + test_midiStream(MIDIMAPPER, hwnd); +} + +START_TEST(midi) +{ + HWND hwnd = 0; + if (1) /* select 1 for CALLBACK_WINDOW or 0 for CALLBACK_FUNCTION */ + hwnd = CreateWindowExA(0, "static", "winmm midi test", WS_POPUP, 0,0,100,100, + 0, 0, 0, NULL); + test_midi_infns(hwnd); + test_midi_outfns(hwnd); + if (hwnd) DestroyWindow(hwnd); +} diff --git a/rostests/winetests/winmm/mixer.c b/rostests/winetests/winmm/mixer.c index 59627a7b38f..82eb338a035 100644 --- a/rostests/winetests/winmm/mixer.c +++ b/rostests/winetests/winmm/mixer.c @@ -184,7 +184,7 @@ static void test_mixerClose(HMIXER mix) mmsys_error(rc)); } -static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) +static void mixer_test_controlA(HMIXEROBJ mix, MIXERCONTROLA *control) { MMRESULT rc; @@ -197,11 +197,18 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) details.dwControlID = control->dwControlID; details.cChannels = 1; U(details).cMultipleItems = 0; - details.paDetails = &value; details.cbDetails = sizeof(value); + /* test NULL paDetails */ + details.paDetails = NULL; + rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE); + ok(rc==MMSYSERR_INVALPARAM, + "mixerGetDevCapsA: MMSYSERR_INVALPARAM expected, got %s\n", + mmsys_error(rc)); + /* read the current control value */ - rc=mixerGetControlDetails((HMIXEROBJ)mix,&details,MIXER_GETCONTROLDETAILSF_VALUE); + details.paDetails = &value; + rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -224,7 +231,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) new_details.cbDetails = sizeof(new_value); /* change the control value by one step */ - rc=mixerSetControlDetails((HMIXEROBJ)mix,&new_details,MIXER_SETCONTROLDETAILSF_VALUE); + rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -240,7 +247,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) ret_details.cbDetails = sizeof(ret_value); /* read back the new control value */ - rc=mixerGetControlDetails((HMIXEROBJ)mix,&ret_details,MIXER_GETCONTROLDETAILSF_VALUE); + rc = mixerGetControlDetailsA(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -259,7 +266,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) details.cbDetails = sizeof(value); /* restore original value */ - rc=mixerSetControlDetails((HMIXEROBJ)mix,&details,MIXER_SETCONTROLDETAILSF_VALUE); + rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -280,7 +287,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) details.paDetails = &value; details.cbDetails = sizeof(value); - rc=mixerGetControlDetails((HMIXEROBJ)mix,&details,MIXER_GETCONTROLDETAILSF_VALUE); + rc = mixerGetControlDetailsA(mix, &details, MIXER_GETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -303,7 +310,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) new_details.cbDetails = sizeof(new_value); /* change the control value by one step */ - rc=mixerSetControlDetails((HMIXEROBJ)mix,&new_details,MIXER_SETCONTROLDETAILSF_VALUE); + rc = mixerSetControlDetails(mix, &new_details, MIXER_SETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -319,7 +326,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) ret_details.cbDetails = sizeof(ret_value); /* read back the new control value */ - rc=mixerGetControlDetails((HMIXEROBJ)mix,&ret_details,MIXER_GETCONTROLDETAILSF_VALUE); + rc = mixerGetControlDetailsA(mix, &ret_details, MIXER_GETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerGetControlDetails(MIXER_GETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -338,7 +345,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) details.cbDetails = sizeof(value); /* restore original value */ - rc=mixerSetControlDetails((HMIXEROBJ)mix,&details,MIXER_SETCONTROLDETAILSF_VALUE); + rc = mixerSetControlDetails(mix, &details, MIXER_SETCONTROLDETAILSF_VALUE); ok(rc==MMSYSERR_NOERROR,"mixerSetControlDetails(MIXER_SETCONTROLDETAILSF_VALUE): " "MMSYSERR_NOERROR expected, got %s\n", mmsys_error(rc)); @@ -354,7 +361,7 @@ static void mixer_test_controlA(HMIXER mix, LPMIXERCONTROLA control) static void mixer_test_deviceA(int device) { MIXERCAPSA capsA; - HMIXER mix; + HMIXEROBJ mix; MMRESULT rc; DWORD d,s,ns,nc; @@ -384,16 +391,23 @@ static void mixer_test_deviceA(int device) capsA.vDriverVersion & 0xff,capsA.wMid,capsA.wPid); } - rc=mixerOpen(&mix, device, 0, 0, 0); + rc = mixerOpen((HMIXER*)&mix, device, 0, 0, 0); ok(rc==MMSYSERR_NOERROR, "mixerOpen: MMSYSERR_NOERROR expected, got %s\n",mmsys_error(rc)); if (rc==MMSYSERR_NOERROR) { + MIXERCAPSA capsA2; + + rc=mixerGetDevCapsA((UINT_PTR)mix,&capsA2,sizeof(capsA2)); + ok(rc==MMSYSERR_NOERROR, + "mixerGetDevCapsA: MMSYSERR_NOERROR expected, got %s\n", + mmsys_error(rc)); + ok(!strcmp(capsA2.szPname, capsA.szPname), "Got wrong device caps\n"); + for (d=0;d #include #include "windef.h" @@ -45,42 +44,65 @@ static DWORD RIFF_buf[] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static void expect_buf_offset_dbg(HMMIO hmmio, LONG off, int line) +{ + MMIOINFO mmio; + LONG ret; + + memset(&mmio, 0, sizeof(mmio)); + ret = mmioGetInfo(hmmio, &mmio, 0); + ok_(__FILE__, line)(ret == MMSYSERR_NOERROR, "mmioGetInfo error %u\n", ret); + ok_(__FILE__, line)(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok_(__FILE__, line)(ret == off, "expected %d, got %d\n", off, ret); +} + +#define expect_buf_offset(a1, a2) expect_buf_offset_dbg(a1, a2, __LINE__) + static void test_mmioDescend(char *fname) { MMRESULT ret; HMMIO hmmio; MMIOINFO mmio; - MMCKINFO ckRiff, ckList, ck; + MMCKINFO ckRiff, ckList, ck, ckList2; memset(&mmio, 0, sizeof(mmio)); mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = sizeof(RIFF_buf); mmio.pchBuffer = (char *)RIFF_buf; - hmmio = mmioOpen(fname, &mmio, MMIO_READ); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); if (fname && !hmmio) { - skip("%s file is missing, skipping the test\n", fname); + trace("No optional %s file. Skipping the test\n", fname); return; } - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + + expect_buf_offset(hmmio, 0); /* first normal RIFF AVI parsing */ ret = mmioDescend(hmmio, &ckRiff, NULL, 0); ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret); ok(ckRiff.ckid == FOURCC_RIFF, "wrong ckid: %04x\n", ckRiff.ckid); ok(ckRiff.fccType == formtypeAVI, "wrong fccType: %04x\n", ckRiff.fccType); + ok(ckRiff.dwDataOffset == 8, "expected 8 got %u\n", ckRiff.dwDataOffset); trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n", (LPCSTR)&ckRiff.ckid, ckRiff.cksize, (LPCSTR)&ckRiff.fccType, ckRiff.dwDataOffset, ckRiff.dwFlags); + expect_buf_offset(hmmio, 12); + ret = mmioDescend(hmmio, &ckList, &ckRiff, 0); ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret); ok(ckList.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList.ckid); ok(ckList.fccType == listtypeAVIHEADER, "wrong fccType: %04x\n", ckList.fccType); + ok(ckList.dwDataOffset == 20, "expected 20 got %u\n", ckList.dwDataOffset); trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n", (LPCSTR)&ckList.ckid, ckList.cksize, (LPCSTR)&ckList.fccType, ckList.dwDataOffset, ckList.dwFlags); + expect_buf_offset(hmmio, 24); + ret = mmioDescend(hmmio, &ck, &ckList, 0); ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret); ok(ck.ckid == ckidAVIMAINHDR, "wrong ckid: %04x\n", ck.ckid); @@ -89,18 +111,23 @@ static void test_mmioDescend(char *fname) (LPCSTR)&ck.ckid, ck.cksize, (LPCSTR)&ck.fccType, ck.dwDataOffset, ck.dwFlags); + expect_buf_offset(hmmio, 32); + /* Skip chunk data */ - mmioSeek(hmmio, ck.cksize, SEEK_CUR); + ret = mmioSeek(hmmio, ck.cksize, SEEK_CUR); + ok(ret == 0x58, "expected 0x58, got %#x\n", ret); - ret = mmioDescend(hmmio, &ckList, &ckList, 0); + ret = mmioDescend(hmmio, &ckList2, &ckList, 0); ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret); - ok(ckList.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList.ckid); - ok(ckList.fccType == listtypeSTREAMHEADER, "wrong fccType: %04x\n", ckList.fccType); + ok(ckList2.ckid == FOURCC_LIST, "wrong ckid: %04x\n", ckList2.ckid); + ok(ckList2.fccType == listtypeSTREAMHEADER, "wrong fccType: %04x\n", ckList2.fccType); trace("ckid %4.4s cksize %04x fccType %4.4s off %04x flags %04x\n", - (LPCSTR)&ckList.ckid, ckList.cksize, (LPCSTR)&ckList.fccType, - ckList.dwDataOffset, ckList.dwFlags); + (LPCSTR)&ckList2.ckid, ckList2.cksize, (LPCSTR)&ckList2.fccType, + ckList2.dwDataOffset, ckList2.dwFlags); - ret = mmioDescend(hmmio, &ck, &ckList, 0); + expect_buf_offset(hmmio, 100); + + ret = mmioDescend(hmmio, &ck, &ckList2, 0); ok(ret == MMSYSERR_NOERROR, "mmioDescend error %u\n", ret); ok(ck.ckid == ckidSTREAMHEADER, "wrong ckid: %04x\n", ck.ckid); ok(ck.fccType == 0, "wrong fccType: %04x\n", ck.fccType); @@ -108,6 +135,8 @@ static void test_mmioDescend(char *fname) (LPCSTR)&ck.ckid, ck.cksize, (LPCSTR)&ck.fccType, ck.dwDataOffset, ck.dwFlags); + expect_buf_offset(hmmio, 108); + /* test various mmioDescend flags */ mmioSeek(hmmio, 0, SEEK_SET); @@ -199,7 +228,7 @@ static void test_mmioDescend(char *fname) static void test_mmioOpen(char *fname) { - char buf[256]; + char buf[MMIO_DEFAULTBUFFER]; MMRESULT ret; HMMIO hmmio; MMIOINFO mmio; @@ -208,13 +237,13 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = sizeof(buf); mmio.pchBuffer = buf; - hmmio = mmioOpen(fname, &mmio, MMIO_READ); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); if (fname && !hmmio) { - skip("%s file is missing, skipping the test\n", fname); + trace("No optional %s file. Skipping the test\n", fname); return; } - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -224,6 +253,17 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == sizeof(buf), "got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -231,8 +271,8 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = 0; mmio.pchBuffer = buf; - hmmio = mmioOpen(fname, &mmio, MMIO_READ); - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -242,6 +282,14 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 0, "expected 0, got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -249,8 +297,8 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = 0; mmio.pchBuffer = NULL; - hmmio = mmioOpen(fname, &mmio, MMIO_READ); - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -260,6 +308,14 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 0, "expected 0, got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer == NULL, "expected NULL\n"); + ok(mmio.pchNext == NULL, "expected NULL\n"); + ok(mmio.pchEndRead == NULL, "expected NULL\n"); + ok(mmio.pchEndWrite == NULL, "expected NULL\n"); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -267,8 +323,8 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = 256; mmio.pchBuffer = NULL; - hmmio = mmioOpen(fname, &mmio, MMIO_READ); - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -278,6 +334,17 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 256, "expected 256, got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer != NULL, "expected not NULL\n"); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -285,8 +352,8 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = sizeof(buf); mmio.pchBuffer = buf; - hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -296,6 +363,17 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == sizeof(buf), "got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -303,8 +381,8 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = 0; mmio.pchBuffer = NULL; - hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -314,6 +392,17 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == MMIO_DEFAULTBUFFER, "expected MMIO_DEFAULTBUFFER, got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer != NULL, "expected not NULL\n"); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -321,8 +410,8 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = 256; mmio.pchBuffer = NULL; - hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -332,6 +421,17 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 256, "expected 256, got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer != NULL, "expected not NULL\n"); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); @@ -339,14 +439,14 @@ static void test_mmioOpen(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = 0; mmio.pchBuffer = buf; - hmmio = mmioOpen(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ | MMIO_ALLOCBUF); if (!hmmio && mmio.wErrorRet == ERROR_BAD_FORMAT) { /* Seen on Win9x, WinMe but also XP-SP1 */ skip("Some Windows versions don't like a 0 size and a given buffer\n"); return; } - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -356,6 +456,17 @@ static void test_mmioOpen(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == MMIO_DEFAULTBUFFER, "expected MMIO_DEFAULTBUFFER, got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); } @@ -371,13 +482,13 @@ static void test_mmioSetBuffer(char *fname) mmio.fccIOProc = fname ? FOURCC_DOS : FOURCC_MEM; mmio.cchBuffer = sizeof(buf); mmio.pchBuffer = buf; - hmmio = mmioOpen(fname, &mmio, MMIO_READ); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); if (fname && !hmmio) { - skip("%s file is missing, skipping the test\n", fname); + trace("No optional %s file. Skipping the test\n", fname); return; } - ok(hmmio != 0, "mmioOpen error %u\n", mmio.wErrorRet); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); memset(&mmio, 0, sizeof(mmio)); ret = mmioGetInfo(hmmio, &mmio, 0); @@ -387,6 +498,17 @@ static void test_mmioSetBuffer(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == sizeof(buf), "got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + if (mmio.fccIOProc == FOURCC_DOS) + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + else + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); ret = mmioSetBuffer(hmmio, NULL, 0, 0); ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret); @@ -399,6 +521,14 @@ static void test_mmioSetBuffer(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 0, "got not 0\n"); ok(mmio.pchBuffer == NULL, "got not NULL buf\n"); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); ret = mmioSetBuffer(hmmio, NULL, 0, MMIO_ALLOCBUF); ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret); @@ -411,6 +541,14 @@ static void test_mmioSetBuffer(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 0, "got not 0\n"); ok(mmio.pchBuffer == NULL, "got not NULL buf\n"); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); ret = mmioSetBuffer(hmmio, buf, 0, MMIO_ALLOCBUF); ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret); @@ -423,6 +561,14 @@ static void test_mmioSetBuffer(char *fname) ok(mmio.fccIOProc == (fname ? FOURCC_DOS : FOURCC_MEM), "got %4.4s\n", (LPCSTR)&mmio.fccIOProc); ok(mmio.cchBuffer == 0, "got not 0\n"); ok(mmio.pchBuffer == buf, "expected %p, got %p\n", buf, mmio.pchBuffer); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); ret = mmioSetBuffer(hmmio, NULL, 256, MMIO_WRITE|MMIO_ALLOCBUF); ok(ret == MMSYSERR_NOERROR, "mmioSetBuffer error %u\n", ret); @@ -436,12 +582,336 @@ static void test_mmioSetBuffer(char *fname) ok(mmio.cchBuffer == 256, "got %u\n", mmio.cchBuffer); ok(mmio.pchBuffer != NULL, "expected not NULL\n"); ok(mmio.pchBuffer != buf, "expected != buf\n"); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", buf, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected 0, got %d\n", mmio.lDiskOffset); + + ret = mmioSeek(hmmio, 0, SEEK_CUR); + ok(ret == 0, "expected 0, got %d\n", ret); mmioClose(hmmio, 0); } +#define FOURCC_XYZ mmioFOURCC('X', 'Y', 'Z', ' ') + +static LRESULT CALLBACK mmio_test_IOProc(LPSTR lpMMIOInfo, UINT uMessage, LPARAM lParam1, LPARAM lParam2) +{ + LPMMIOINFO lpInfo = (LPMMIOINFO) lpMMIOInfo; + + switch (uMessage) + { + case MMIOM_OPEN: + if (lpInfo->fccIOProc == FOURCC_DOS) + lpInfo->fccIOProc = mmioFOURCC('F', 'A', 'I', 'L'); + return MMSYSERR_NOERROR; + case MMIOM_CLOSE: + return MMSYSERR_NOERROR; + case MMIOM_SEEK: + lpInfo->adwInfo[1]++; + lpInfo->lDiskOffset = 0xdeadbeef; + return 0; + default: + return 0; + } +} + +static void test_mmioOpen_fourcc(void) +{ + char fname[] = "file+name.xyz+one.two"; + + LPMMIOPROC lpProc; + HMMIO hmmio; + MMIOINFO mmio; + + lpProc = mmioInstallIOProcA(FOURCC_DOS, mmio_test_IOProc, MMIO_INSTALLPROC); + ok(lpProc == mmio_test_IOProc, "mmioInstallIOProcA error\n"); + + lpProc = mmioInstallIOProcA(FOURCC_XYZ, mmio_test_IOProc, MMIO_INSTALLPROC); + ok(lpProc == mmio_test_IOProc, "mmioInstallIOProcA error\n"); + + memset(&mmio, 0, sizeof(mmio)); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); + mmioGetInfo(hmmio, &mmio, 0); + ok(hmmio && mmio.fccIOProc == FOURCC_XYZ, "mmioOpenA error %u, got %4.4s\n", + mmio.wErrorRet, (LPCSTR)&mmio.fccIOProc); + ok(mmio.adwInfo[1] == 0, "mmioOpenA sent MMIOM_SEEK, got %d\n", + mmio.adwInfo[1]); + ok(mmio.lDiskOffset == 0, "mmioOpenA updated lDiskOffset, got %d\n", + mmio.lDiskOffset); + mmioClose(hmmio, 0); + + mmioInstallIOProcA(FOURCC_XYZ, NULL, MMIO_REMOVEPROC); + + memset(&mmio, 0, sizeof(mmio)); + hmmio = mmioOpenA(fname, &mmio, MMIO_READ); + mmioGetInfo(hmmio, &mmio, 0); + ok(!hmmio && mmio.wErrorRet == MMIOERR_FILENOTFOUND, "mmioOpenA error %u, got %4.4s\n", + mmio.wErrorRet, (LPCSTR)&mmio.fccIOProc); + mmioClose(hmmio, 0); + + mmioInstallIOProcA(FOURCC_DOS, NULL, MMIO_REMOVEPROC); +} + +static BOOL create_test_file(char *temp_file) +{ + char temp_path[MAX_PATH]; + DWORD ret, written; + HANDLE h; + + ret = GetTempPathA(sizeof(temp_path), temp_path); + ok(ret, "Failed to get a temp path, err %d\n", GetLastError()); + if (!ret) + return FALSE; + + ret = GetTempFileNameA(temp_path, "mmio", 0, temp_file); + ok(ret, "Failed to get a temp name, err %d\n", GetLastError()); + if (!ret) + return FALSE; + + h = CreateFileA(temp_file, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + ok(h != INVALID_HANDLE_VALUE, "Failed to create a file, err %d\n", GetLastError()); + if (h == INVALID_HANDLE_VALUE) return FALSE; + + ret = WriteFile(h, RIFF_buf, sizeof(RIFF_buf), &written, NULL); + ok(ret, "Failed to write a file, err %d\n", GetLastError()); + CloseHandle(h); + if (!ret) DeleteFileA(temp_file); + return ret; +} + +static void test_mmioSeek(void) +{ + HMMIO hmmio; + MMIOINFO mmio; + LONG end, pos; + const LONG size = sizeof(RIFF_buf), offset = 16; + char test_file[MAX_PATH]; + MMRESULT res; + HFILE hfile; + OFSTRUCT ofs; + + /* test memory file */ + memset(&mmio, 0, sizeof(mmio)); + mmio.fccIOProc = FOURCC_MEM; + mmio.pchBuffer = (char*)&RIFF_buf; + mmio.cchBuffer = sizeof(RIFF_buf); + hmmio = mmioOpenA(NULL, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + if (hmmio != NULL) { + /* seek to the end */ + end = mmioSeek(hmmio, 0, SEEK_END); + ok(end == size, "expected %d, got %d\n", size, end); + + /* test MMIOINFO values */ + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.pchNext == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == 0, "expected %d, got %d\n", 0, mmio.lBufOffset); + ok(mmio.lDiskOffset == 0, "expected %d, got %d\n", 0, mmio.lDiskOffset); + + /* seek backward from the end */ + pos = mmioSeek(hmmio, offset, SEEK_END); + ok(pos == size-offset, "expected %d, got %d\n", size-offset, pos); + + mmioClose(hmmio, 0); + } + + if (!create_test_file(test_file)) return; + + /* test standard file without buffering */ + hmmio = NULL; + memset(&mmio, 0, sizeof(mmio)); + mmio.fccIOProc = FOURCC_DOS; + mmio.pchBuffer = 0; + mmio.cchBuffer = 0; + hmmio = mmioOpenA(test_file, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + if (hmmio != NULL) { + /* seek to the end */ + end = mmioSeek(hmmio, 0, SEEK_END); + ok(end == size, "expected %d, got %d\n", size, end); + + /* test MMIOINFO values */ + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == size, "expected %d, got %d\n", size, mmio.lBufOffset); + ok(mmio.lDiskOffset == size, "expected %d, got %d\n", size, mmio.lDiskOffset); + + /* seek backward from the end */ + pos = mmioSeek(hmmio, offset, SEEK_END); + ok(pos == size-offset, "expected %d, got %d\n", size-offset, pos); + + mmioClose(hmmio, 0); + } + + /* test standard file with buffering */ + hmmio = NULL; + memset(&mmio, 0, sizeof(mmio)); + mmio.fccIOProc = FOURCC_DOS; + mmio.pchBuffer = 0; + mmio.cchBuffer = 0; + hmmio = mmioOpenA(test_file, &mmio, MMIO_READ | MMIO_ALLOCBUF); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + if (hmmio != NULL) { + /* seek to the end */ + end = mmioSeek(hmmio, 0, SEEK_END); + ok(end == size, "expected %d, got %d\n", size, end); + + /* test MMIOINFO values */ + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + ok(mmio.pchEndWrite == mmio.pchBuffer + mmio.cchBuffer, "expected %p + %d, got %p\n", mmio.pchBuffer, mmio.cchBuffer, mmio.pchEndWrite); + ok(mmio.lBufOffset == end, "expected %d, got %d\n", end, mmio.lBufOffset); + ok(mmio.lDiskOffset == size, "expected %d, got %d\n", size, mmio.lDiskOffset); + + /* seek backward from the end */ + pos = mmioSeek(hmmio, offset, SEEK_END); + ok(pos == size-offset, "expected %d, got %d\n", size-offset, pos); + + mmioClose(hmmio, 0); + } + + /* test seek position inheritance from standard file handle */ + hfile = OpenFile(test_file, &ofs, OF_READ); + ok(hfile != HFILE_ERROR, "Failed to open the file, err %d\n", GetLastError()); + if (hfile != HFILE_ERROR) { + pos = _llseek(hfile, offset, SEEK_SET); + ok(pos != HFILE_ERROR, "Failed to seek, err %d\n", GetLastError()); + memset(&mmio, 0, sizeof(mmio)); + mmio.fccIOProc = FOURCC_DOS; + mmio.adwInfo[0] = (DWORD)hfile; + hmmio = mmioOpenA(NULL, &mmio, MMIO_READ | MMIO_DENYWRITE | MMIO_ALLOCBUF); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + if (hmmio != NULL) { + pos = mmioSeek(hmmio, 0, SEEK_CUR); + ok(pos == offset, "expected %d, got %d\n", offset, pos); + mmioClose(hmmio, 0); + } + } + + DeleteFileA(test_file); +} + +static void test_mmio_end_of_file(void) +{ + char test_file[MAX_PATH], buffer[128], data[16]; + MMIOINFO mmio; + HMMIO hmmio; + LONG ret; + MMRESULT res; + + if (!create_test_file(test_file)) return; + + memset(&mmio, 0, sizeof(mmio)); + mmio.fccIOProc = FOURCC_DOS; + mmio.pchBuffer = buffer; + mmio.cchBuffer = sizeof(buffer); + hmmio = mmioOpenA(test_file, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + if (hmmio == NULL) { + DeleteFileA(test_file); + return; + } + + ret = mmioSeek(hmmio, 0, SEEK_END); + ok(sizeof(RIFF_buf) == ret, "got %d\n", ret); + + ret = mmioRead(hmmio, data, sizeof(data)); + ok(ret == 0, "expected %d, got %d\n", 0, ret); + + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + + res = mmioAdvance(hmmio, &mmio, MMIO_READ); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.pchNext == mmio.pchEndRead, "expected %p, got %p\n", mmio.pchEndRead, mmio.pchNext); + + mmioClose(hmmio, 0); + DeleteFileA(test_file); +} + +static void test_mmio_buffer_pointer(void) +{ + char test_file[MAX_PATH]; + char buffer[5], data[16]; + MMIOINFO mmio; + HMMIO hmmio; + LONG size, pos; + MMRESULT res; + + if (!create_test_file(test_file)) return; + + memset(&mmio, 0, sizeof(mmio)); + mmio.fccIOProc = FOURCC_DOS; + mmio.pchBuffer = buffer; + mmio.cchBuffer = sizeof(buffer); + hmmio = mmioOpenA(test_file, &mmio, MMIO_READ); + ok(hmmio != 0, "mmioOpenA error %u\n", mmio.wErrorRet); + if (hmmio == NULL) { + DeleteFileA(test_file); + return; + } + + /* the buffer is empty */ + size = mmioRead(hmmio, data, 0); + ok(size == 0, "expected 0, got %d\n", size); + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + + /* fill the buffer */ + size = mmioAdvance(hmmio, &mmio, MMIO_READ); + ok(mmio.pchEndRead-mmio.pchBuffer == sizeof(buffer), "got %d\n", (int)(mmio.pchEndRead-mmio.pchBuffer)); + + /* seeking to the same buffer chunk, the buffer is kept */ + size = sizeof(buffer)/2; + pos = mmioSeek(hmmio, size, SEEK_SET); + ok(pos == size, "failed to seek, expected %d, got %d\n", size, pos); + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.lBufOffset == 0, "expected 0, got %d\n", mmio.lBufOffset); + ok(mmio.pchNext-mmio.pchBuffer == size, "expected %d, got %d\n", size, (int)(mmio.pchNext-mmio.pchBuffer)); + ok(mmio.pchEndRead-mmio.pchBuffer == sizeof(buffer), "got %d\n", (int)(mmio.pchEndRead-mmio.pchBuffer)); + + /* seeking to another buffer chunk, the buffer is empty */ + size = sizeof(buffer) * 3 + sizeof(buffer) / 2; + pos = mmioSeek(hmmio, size, SEEK_SET); + ok(pos == size, "failed to seek, got %d\n", pos); + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.lBufOffset == size, "expected %d, got %d\n", size, mmio.lBufOffset); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + + /* reading a lot (as sizeof(data) > mmio.cchBuffer), the buffer is empty */ + size = mmioRead(hmmio, data, sizeof(data)); + ok(size == sizeof(data), "failed to read, got %d\n", size); + res = mmioGetInfo(hmmio, &mmio, 0); + ok(res == MMSYSERR_NOERROR, "expected 0, got %d\n", res); + ok(mmio.lBufOffset == pos+size, "expected %d, got %d\n", pos+size, mmio.lBufOffset); + ok(mmio.pchNext == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchNext); + ok(mmio.pchEndRead == mmio.pchBuffer, "expected %p, got %p\n", mmio.pchBuffer, mmio.pchEndRead); + + mmioClose(hmmio, 0); + DeleteFileA(test_file); +} + START_TEST(mmio) { + /* Make it possible to run the tests against a specific AVI file in + * addition to the builtin test data. This is mostly meant as a + * debugging aid and is not part of the standard tests. + */ char fname[] = "msrle.avi"; test_mmioDescend(NULL); @@ -450,4 +920,8 @@ START_TEST(mmio) test_mmioOpen(fname); test_mmioSetBuffer(NULL); test_mmioSetBuffer(fname); + test_mmioOpen_fourcc(); + test_mmioSeek(); + test_mmio_end_of_file(); + test_mmio_buffer_pointer(); } diff --git a/rostests/winetests/winmm/testlist.c b/rostests/winetests/winmm/testlist.c index e61d92d6422..16758943534 100644 --- a/rostests/winetests/winmm/testlist.c +++ b/rostests/winetests/winmm/testlist.c @@ -1,13 +1,13 @@ /* Automatically generated file; DO NOT EDIT!! */ -#define WIN32_LEAN_AND_MEAN -#include - #define STANDALONE -#include "wine/test.h" +#include extern void func_capture(void); +extern void func_joystick(void); extern void func_mci(void); +extern void func_mcicda(void); +extern void func_midi(void); extern void func_mixer(void); extern void func_mmio(void); extern void func_timer(void); @@ -16,7 +16,10 @@ extern void func_wave(void); const struct test winetest_testlist[] = { { "capture", func_capture }, + { "joystick", func_joystick }, { "mci", func_mci }, + { "mcicda", func_mcicda }, + { "midi", func_midi }, { "mixer", func_mixer }, { "mmio", func_mmio }, { "timer", func_timer }, diff --git a/rostests/winetests/winmm/timer.c b/rostests/winetests/winmm/timer.c index 7ac86b99371..be63f6b032a 100644 --- a/rostests/winetests/winmm/timer.c +++ b/rostests/winetests/winmm/timer.c @@ -190,6 +190,7 @@ static void test_priority(void) "thread priority is %s, should be THREAD_PRIORITY_TIME_CRITICAL\n", get_priority(priority)); } + timeKillEvent(id); } START_TEST(timer) diff --git a/rostests/winetests/winmm/wave.c b/rostests/winetests/winmm/wave.c index 6a4786a5951..fc2bab072d1 100644 --- a/rostests/winetests/winmm/wave.c +++ b/rostests/winetests/winmm/wave.c @@ -30,12 +30,20 @@ #include "winnls.h" #include "mmsystem.h" #define NOBITMAP +#include "mmddk.h" #include "mmreg.h" -#include "ks.h" -#include "ksmedia.h" +//#include "ks.h" +//#include "ksguid.h" +//#include "ksmedia.h" #include "winmm_test.h" +/* FIXME */ +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); +DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); + +static DWORD g_tid; + static void test_multiple_waveopens(void) { HWAVEOUT handle1, handle2; @@ -262,10 +270,9 @@ const char* wave_out_error(MMRESULT error) static char long_msg[1100]; MMRESULT rc; - rc = waveOutGetErrorText(error, msg, sizeof(msg)); + rc = waveOutGetErrorTextA(error, msg, sizeof(msg)); if (rc != MMSYSERR_NOERROR) - sprintf(long_msg, "waveOutGetErrorText(%x) failed with error %x", - error, rc); + sprintf(long_msg, "waveOutGetErrorTextA(%x) failed with error %x", error, rc); else sprintf(long_msg, "%s(%s)", mmsys_error(error), msg); return long_msg; @@ -274,7 +281,7 @@ const char* wave_out_error(MMRESULT error) const char * wave_open_flags(DWORD flags) { static char msg[1024]; - int first = TRUE; + BOOL first = TRUE; msg[0] = 0; if ((flags & CALLBACK_TYPEMASK) == CALLBACK_EVENT) { strcat(msg, "CALLBACK_EVENT"); @@ -318,7 +325,6 @@ const char * wave_open_flags(DWORD flags) if ((flags & WAVE_MAPPED) == WAVE_MAPPED) { if (!first) strcat(msg, "|"); strcat(msg, "WAVE_MAPPED"); - first = FALSE; } return msg; } @@ -327,7 +333,7 @@ static const char * wave_header_flags(DWORD flags) { #define WHDR_MASK (WHDR_BEGINLOOP|WHDR_DONE|WHDR_ENDLOOP|WHDR_INQUEUE|WHDR_PREPARED) static char msg[1024]; - int first = TRUE; + BOOL first = TRUE; msg[0] = 0; if (flags & WHDR_BEGINLOOP) { strcat(msg, "WHDR_BEGINLOOP"); @@ -468,10 +474,10 @@ DWORD time_to_bytes(LPMMTIME mmtime, LPWAVEFORMATEX pwfx) else if (mmtime->wType == TIME_MS) return mmtime->u.ms * pwfx->nAvgBytesPerSec / 1000; else if (mmtime->wType == TIME_SMPTE) - return ((mmtime->u.smpte.hour * 60.0 * 60.0) + - (mmtime->u.smpte.min * 60.0) + - (mmtime->u.smpte.sec) + - (mmtime->u.smpte.frame / 30.0)) * pwfx->nAvgBytesPerSec; + return ((mmtime->u.smpte.hour * 60 * 60) + + (mmtime->u.smpte.min * 60) + + (mmtime->u.smpte.sec)) * pwfx->nAvgBytesPerSec + + mmtime->u.smpte.frame * pwfx->nAvgBytesPerSec / 30; trace("FIXME: time_to_bytes() type not supported\n"); return -1; @@ -481,16 +487,16 @@ static void check_position(int device, HWAVEOUT wout, DWORD bytes, LPWAVEFORMATEX pwfx ) { MMTIME mmtime; - DWORD samples; - double duration; MMRESULT rc; DWORD returned; - samples=bytes/(pwfx->wBitsPerSample/8*pwfx->nChannels); - duration=((double)samples)/pwfx->nSamplesPerSec; + mmtime.wType = TIME_BYTES; + rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime) - 1); + ok(rc==MMSYSERR_ERROR, + "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc)); mmtime.wType = TIME_BYTES; - rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime)); + rc=waveOutGetPosition(wout, &mmtime, sizeof(mmtime) + 1); ok(rc==MMSYSERR_NOERROR, "waveOutGetPosition(%s): rc=%s\n",dev_name(device),wave_out_error(rc)); if (mmtime.wType != TIME_BYTES && winetest_debug > 1) @@ -564,6 +570,8 @@ static void CALLBACK callback_func(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2) { + if(uMsg == WOM_OPEN || uMsg == WOM_CLOSE) + ok(GetCurrentThreadId() == g_tid, "Got different thread ID\n"); SetEvent((HANDLE)dwInstance); } @@ -574,12 +582,12 @@ static DWORD WINAPI callback_thread(LPVOID lpParameter) PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE ); /* make sure the thread has a message queue */ SetEvent(lpParameter); - while (GetMessage(&msg, 0, 0, 0)) { + while (GetMessageA(&msg, 0, 0, 0)) { UINT message = msg.message; /* for some reason XP sends a WM_USER message before WOM_OPEN */ ok (message == WOM_OPEN || message == WOM_DONE || message == WOM_CLOSE || message == WM_USER || message == WM_APP, - "GetMessage returned unexpected message: %u\n", message); + "GetMessageA returned unexpected message: %u\n", message); if (message == WOM_OPEN || message == WOM_DONE || message == WOM_CLOSE) SetEvent(lpParameter); else if (message == WM_APP) { @@ -591,21 +599,19 @@ static DWORD WINAPI callback_thread(LPVOID lpParameter) return 0; } -static void wave_out_test_deviceOut(int device, double duration, - int headers, int loops, - LPWAVEFORMATEX pwfx, DWORD format, - DWORD flags, LPWAVEOUTCAPS pcaps, - BOOL interactive, BOOL sine, BOOL pause) +static void wave_out_test_deviceOut(int device, double duration, int headers, int loops, + WAVEFORMATEX *pwfx, DWORD format, DWORD flags, WAVEOUTCAPSA *pcaps, BOOL interactive, + BOOL sine, BOOL pause) { HWAVEOUT wout; - HANDLE hevent; + HANDLE hevent = CreateEventW(NULL, FALSE, FALSE, NULL); WAVEHDR *frags = 0; MMRESULT rc; DWORD volume; WORD nChannels = pwfx->nChannels; WORD wBitsPerSample = pwfx->wBitsPerSample; DWORD nSamplesPerSec = pwfx->nSamplesPerSec; - BOOL has_volume = pcaps->dwSupport & WAVECAPS_VOLUME ? TRUE : FALSE; + BOOL has_volume = (pcaps->dwSupport & WAVECAPS_VOLUME) != 0; double paused = 0.0; DWORD_PTR callback = 0; DWORD_PTR callback_instance = 0; @@ -616,11 +622,6 @@ static void wave_out_test_deviceOut(int device, double duration, DWORD frag_length; int i, j; - hevent=CreateEvent(NULL,FALSE,FALSE,NULL); - ok(hevent!=NULL,"CreateEvent(): error=%d\n",GetLastError()); - if (hevent==NULL) - return; - if ((flags & CALLBACK_TYPEMASK) == CALLBACK_EVENT) { callback = (DWORD_PTR)hevent; callback_instance = 0; @@ -653,6 +654,7 @@ static void wave_out_test_deviceOut(int device, double duration, return; } wout=NULL; + g_tid = GetCurrentThreadId(); rc=waveOutOpen(&wout,device,pwfx,callback,callback_instance,flags); /* Note: Win9x doesn't know WAVE_FORMAT_DIRECT */ /* It is acceptable to fail on formats that are not specified to work */ @@ -665,13 +667,13 @@ static void wave_out_test_deviceOut(int device, double duration, (!(flags & WAVE_FORMAT_DIRECT) || (flags & WAVE_MAPPED)) && !(pcaps->dwFormats & format)) || (rc==MMSYSERR_INVALFLAG && (flags & WAVE_FORMAT_DIRECT)), - "waveOutOpen(%s): format=%dx%2dx%d flags=%lx(%s) rc=%s\n", + "waveOutOpen(%s): format=%dx%2dx%d flags=%x(%s) rc=%s\n", dev_name(device),pwfx->nSamplesPerSec,pwfx->wBitsPerSample, pwfx->nChannels,CALLBACK_EVENT|flags, wave_open_flags(CALLBACK_EVENT|flags),wave_out_error(rc)); if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) && (flags & WAVE_FORMAT_DIRECT) && (pcaps->dwFormats & format)) - trace(" Reason: The device lists this format as supported in it's " + trace(" Reason: The device lists this format as supported in its " "capabilities but opening it failed.\n"); if ((rc==WAVERR_BADFORMAT || rc==MMSYSERR_NOTSUPPORTED) && !(pcaps->dwFormats & format)) @@ -683,7 +685,8 @@ static void wave_out_test_deviceOut(int device, double duration, if (rc!=MMSYSERR_NOERROR) goto EXIT; - WaitForSingleObject(hevent,10000); + rc=WaitForSingleObject(hevent,9000); + ok(rc==WAIT_OBJECT_0, "missing WOM_OPEN notification\n"); ok(pwfx->nChannels==nChannels && pwfx->wBitsPerSample==wBitsPerSample && @@ -726,9 +729,8 @@ static void wave_out_test_deviceOut(int device, double duration, } if (interactive && rc==MMSYSERR_NOERROR) { - DWORD start; trace("Playing %g second %s at %5dx%2dx%d %2d header%s %d loop%s %d bytes %s %s\n",duration, - sine ? "440Hz tone" : "silence",pwfx->nSamplesPerSec, + sine ? "440 Hz tone" : "silence", pwfx->nSamplesPerSec, pwfx->wBitsPerSample,pwfx->nChannels, headers, headers > 1 ? "s": " ", loops, loops == 1 ? " " : "s", length * (loops + 1), get_format_str(pwfx->wFormatTag), @@ -747,8 +749,6 @@ static void wave_out_test_deviceOut(int device, double duration, ok(has_volume ? rc==MMSYSERR_NOERROR : rc==MMSYSERR_NOTSUPPORTED, "waveOutSetVolume(%s): rc=%s\n",dev_name(device),wave_out_error(rc)); - start=GetTickCount(); - rc=waveOutWrite(wout, &frags[0], sizeof(frags[0])); ok(rc==MMSYSERR_NOERROR,"waveOutWrite(%s): rc=%s\n", dev_name(device),wave_out_error(rc)); @@ -789,7 +789,8 @@ static void wave_out_test_deviceOut(int device, double duration, ok(rc==MMSYSERR_NOERROR,"waveOutWrite(%s, header[%d]): rc=%s\n", dev_name(device),(i+1)%headers,wave_out_error(rc)); } - WaitForSingleObject(hevent,10000); + rc=WaitForSingleObject(hevent,8000); + ok(rc==WAIT_OBJECT_0, "missing WOM_DONE notification\n"); } } @@ -809,15 +810,65 @@ static void wave_out_test_deviceOut(int device, double duration, "waveOutUnprepareHeader(%s): rc=%s\n",dev_name(device), wave_out_error(rc)); } - HeapFree(GetProcessHeap(), 0, buffer); + + ok(frags[0].dwFlags==(interactive ? WHDR_DONE : 0), "dwFlags(%d)=%x\n",device,frags[0].dwFlags); + + frags[0].dwFlags |= WHDR_DONE; + rc=waveOutUnprepareHeader(wout, &frags[0], sizeof(frags[0])); + ok(rc==MMSYSERR_NOERROR, "waveOutUnprepareHeader(%d): rc=%s\n",device,wave_out_error(rc)); + ok(frags[0].dwFlags==WHDR_DONE, "dwFlags(%d)=%x\n",device,frags[0].dwFlags); + + frags[0].dwFlags |= WHDR_INQUEUE; + rc=waveOutPrepareHeader(wout, &frags[0], sizeof(frags[0])); + ok(rc==MMSYSERR_NOERROR, "waveOutPrepareHeader(%d): rc=%s\n",device,wave_out_error(rc)); + ok(frags[0].dwFlags==WHDR_PREPARED, "dwFlags(%d)=%x\n",device,frags[0].dwFlags); + + frags[0].dwFlags |= WHDR_INQUEUE; + rc=waveOutPrepareHeader(wout, &frags[0], sizeof(frags[0])); + ok(rc==MMSYSERR_NOERROR, "waveOutPrepareHeader(%d): rc=%s\n",device,wave_out_error(rc)); + ok(frags[0].dwFlags==(WHDR_PREPARED|WHDR_INQUEUE), "dwFlags(%d)=%x\n",device,frags[0].dwFlags); + + frags[0].dwFlags &= ~(WHDR_INQUEUE|WHDR_DONE); + rc=waveOutUnprepareHeader(wout, &frags[0], sizeof(frags[0])); + ok(rc==MMSYSERR_NOERROR, "waveOutUnprepareHeader(%d): rc=%s\n",device,wave_out_error(rc)); + ok(frags[0].dwFlags==0, "dwFlags(%d)=%x\n",device,frags[0].dwFlags); rc=waveOutClose(wout); ok(rc==MMSYSERR_NOERROR,"waveOutClose(%s): rc=%s\n",dev_name(device), wave_out_error(rc)); - WaitForSingleObject(hevent,10000); + if (rc==WAVERR_STILLPLAYING) { + /* waveOutReset ought to return all buffers s.t. waveOutClose succeeds */ + rc=waveOutReset(wout); + ok(rc==MMSYSERR_NOERROR,"waveOutReset(%s): rc=%s\n",dev_name(device), + wave_out_error(rc)); + + for (i = 0; i < headers; i++) { + rc=waveOutUnprepareHeader(wout, &frags[i], sizeof(frags[0])); + ok(rc==MMSYSERR_NOERROR, + "waveOutUnprepareHeader(%s): rc=%s\n",dev_name(device), + wave_out_error(rc)); + } + rc=waveOutClose(wout); + ok(rc==MMSYSERR_NOERROR,"waveOutClose(%s): rc=%s\n",dev_name(device), + wave_out_error(rc)); + } + rc=WaitForSingleObject(hevent,1500); + ok(rc==WAIT_OBJECT_0, "missing WOM_CLOSE notification\n"); + + wout = (HWAVEOUT)0xdeadf00d; + rc=waveOutOpen(&wout,device,pwfx,callback,callback_instance,flags|WAVE_FORMAT_QUERY); + ok(rc==MMSYSERR_NOERROR, "WAVE_FORMAT_QUERY(%s): rc=%s\n",dev_name(device), + wave_out_error(rc)); + ok(wout==(HWAVEOUT)0xdeadf00d, "WAVE_FORMAT_QUERY handle %p\n", wout); + + rc=WaitForSingleObject(hevent,20); + ok(rc==WAIT_TIMEOUT, "Notification from %s rc=%x\n", + wave_open_flags(flags|WAVE_FORMAT_QUERY),rc); + + HeapFree(GetProcessHeap(), 0, buffer); EXIT: if ((flags & CALLBACK_TYPEMASK) == CALLBACK_THREAD) { - PostThreadMessage(thread_id, WM_APP, 0, 0); + PostThreadMessageW(thread_id, WM_APP, 0, 0); WaitForSingleObject(hevent,10000); } CloseHandle(hevent); @@ -828,7 +879,7 @@ static void wave_out_test_device(UINT_PTR device) { WAVEOUTCAPSA capsA; WAVEOUTCAPSW capsW; - WAVEFORMATEX format, oformat; + WAVEFORMATEX format; WAVEFORMATEXTENSIBLE wfex; IMAADPCMWAVEFORMAT wfa; HWAVEOUT wout; @@ -894,11 +945,16 @@ static void wave_out_test_device(UINT_PTR device) "waveOutGetDevCapsW(%s): unexpected return value %s\n", dev_name(device),wave_out_error(rc)); + rc=waveOutMessage((HWAVEOUT)device, DRV_QUERYMAPPABLE, 0, 0); + ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_NOTSUPPORTED, + "DRV_QUERYMAPPABLE(%s): unexpected return value %s\n", + dev_name(device),wave_out_error(rc)); + nameA=NULL; rc=waveOutMessage((HWAVEOUT)device, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&size, 0); - ok(rc==MMSYSERR_NOERROR || rc==MMSYSERR_INVALPARAM || - rc==MMSYSERR_NOTSUPPORTED, + ok(rc==MMSYSERR_NOERROR || broken(rc==MMSYSERR_INVALPARAM || + rc==MMSYSERR_NOTSUPPORTED), "waveOutMessage(%s): failed to get interface size, rc=%s\n", dev_name(device),wave_out_error(rc)); if (rc==MMSYSERR_NOERROR) { @@ -967,20 +1023,20 @@ static void wave_out_test_device(UINT_PTR device) format.nBlockAlign=format.nChannels*format.wBitsPerSample/8; format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign; format.cbSize=0; - wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.6,1,0,&format,WAVE_FORMAT_2M08, CALLBACK_EVENT,&capsA,TRUE,FALSE,FALSE); - wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.6,1,0,&format,WAVE_FORMAT_2M08, CALLBACK_EVENT,&capsA,TRUE,FALSE,TRUE); - wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.6,1,0,&format,WAVE_FORMAT_2M08, CALLBACK_FUNCTION,&capsA,TRUE,FALSE,FALSE); - wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.6,1,0,&format,WAVE_FORMAT_2M08, CALLBACK_FUNCTION,&capsA,TRUE,FALSE,TRUE); - wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.6,1,0,&format,WAVE_FORMAT_2M08, CALLBACK_THREAD,&capsA,TRUE,FALSE,FALSE); - wave_out_test_deviceOut(device,1.0,1,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.6,1,0,&format,WAVE_FORMAT_2M08, CALLBACK_THREAD,&capsA,TRUE,FALSE,TRUE); - wave_out_test_deviceOut(device,1.0,10,0,&format,WAVE_FORMAT_2M08, + wave_out_test_deviceOut(device,0.8,10,0,&format,WAVE_FORMAT_2M08, CALLBACK_EVENT,&capsA,TRUE,FALSE,FALSE); wave_out_test_deviceOut(device,1.0,5,1,&format,WAVE_FORMAT_2M08, CALLBACK_EVENT,&capsA,TRUE,FALSE,FALSE); @@ -1111,52 +1167,6 @@ static void wave_out_test_device(UINT_PTR device) VirtualFree(twoPages, 0, MEM_RELEASE); } - /* Testing invalid format: 11 bits per sample */ - format.wFormatTag=WAVE_FORMAT_PCM; - format.nChannels=2; - format.wBitsPerSample=11; - format.nSamplesPerSec=22050; - format.nBlockAlign=format.nChannels*format.wBitsPerSample/8; - format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign; - format.cbSize=0; - oformat=format; - rc=waveOutOpen(&wout,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT); - ok(rc==WAVERR_BADFORMAT || rc==MMSYSERR_INVALFLAG || - rc==MMSYSERR_INVALPARAM, - "waveOutOpen(%s): opening the device in 11 bits mode should fail: " - "rc=%s\n",dev_name(device),wave_out_error(rc)); - if (rc==MMSYSERR_NOERROR) { - trace(" got %dx%2dx%d for %dx%2dx%d\n", - format.nSamplesPerSec, format.wBitsPerSample, - format.nChannels, - oformat.nSamplesPerSec, oformat.wBitsPerSample, - oformat.nChannels); - waveOutClose(wout); - } - - /* Testing invalid format: 2 MHz sample rate */ - format.wFormatTag=WAVE_FORMAT_PCM; - format.nChannels=2; - format.wBitsPerSample=16; - format.nSamplesPerSec=2000000; - format.nBlockAlign=format.nChannels*format.wBitsPerSample/8; - format.nAvgBytesPerSec=format.nSamplesPerSec*format.nBlockAlign; - format.cbSize=0; - oformat=format; - rc=waveOutOpen(&wout,device,&format,0,0,CALLBACK_NULL|WAVE_FORMAT_DIRECT); - ok(rc==WAVERR_BADFORMAT || rc==MMSYSERR_INVALFLAG || - rc==MMSYSERR_INVALPARAM, - "waveOutOpen(%s): opening the device at 2 MHz sample rate should fail: " - "rc=%s\n",dev_name(device),wave_out_error(rc)); - if (rc==MMSYSERR_NOERROR) { - trace(" got %dx%2dx%d for %dx%2dx%d\n", - format.nSamplesPerSec, format.wBitsPerSample, - format.nChannels, - oformat.nSamplesPerSec, oformat.wBitsPerSample, - oformat.nChannels); - waveOutClose(wout); - } - /* try some non PCM formats */ format.wFormatTag=WAVE_FORMAT_MULAW; format.nChannels=1; @@ -1177,10 +1187,23 @@ static void wave_out_test_device(UINT_PTR device) &capsA,winetest_interactive,TRUE,FALSE); wave_out_test_deviceOut(device,1.0,5,1,&format,0,CALLBACK_EVENT, &capsA,winetest_interactive,TRUE,FALSE); - } else + } else { + MMRESULT query_rc; + trace("waveOutOpen(%s): WAVE_FORMAT_MULAW not supported\n", dev_name(device)); + query_rc = waveOutOpen(NULL, device, &format, 0, 0, CALLBACK_NULL | WAVE_FORMAT_QUERY); + ok(query_rc==MMSYSERR_NOERROR || query_rc==WAVERR_BADFORMAT || query_rc==MMSYSERR_INVALPARAM, + "waveOutOpen(%s): returned %s\n",dev_name(device),wave_out_error(rc)); + + rc = waveOutOpen(&wout, device, &format, 0, 0, CALLBACK_NULL); + ok(rc == query_rc, + "waveOutOpen(%s): returned different from query: %s\n",dev_name(device),wave_out_error(rc)); + if(rc == MMSYSERR_NOERROR) + waveOutClose(wout); + } + wfa.wfx.wFormatTag=WAVE_FORMAT_IMA_ADPCM; wfa.wfx.nChannels=1; wfa.wfx.nSamplesPerSec=11025; @@ -1402,11 +1425,22 @@ static void wave_out_tests(void) WAVEFORMATEX format; HWAVEOUT wout; MMRESULT rc; + DWORD preferred, status; UINT ndev,d; ndev=waveOutGetNumDevs(); trace("found %d WaveOut devices\n",ndev); + rc = waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, + (DWORD_PTR)&preferred, (DWORD_PTR)&status); + ok((ndev == 0 && (rc == MMSYSERR_NODRIVER || rc == MMSYSERR_BADDEVICEID)) || + rc == MMSYSERR_NOTSUPPORTED || + rc == MMSYSERR_NOERROR, "waveOutMessage(DRVM_MAPPER_PREFERRED_GET) failed: %u\n", rc); + + if(rc != MMSYSERR_NOTSUPPORTED) + ok((ndev == 0 && (preferred == -1 || broken(preferred != -1))) || + preferred < ndev, "Got invalid preferred device: 0x%x\n", preferred); + rc=waveOutGetDevCapsA(ndev+1,&capsA,sizeof(capsA)); ok(rc==MMSYSERR_BADDEVICEID, "waveOutGetDevCapsA(%s): MMSYSERR_BADDEVICEID expected, got %s\n", @@ -1451,15 +1485,128 @@ static void wave_out_tests(void) "waveOutOpen(%s): MMSYSERR_BADDEVICEID expected, got %s\n", dev_name(ndev+1),mmsys_error(rc)); - for (d=0;d0) wave_out_test_device(WAVE_MAPPER); } +static void test_sndPlaySound(void) +{ + BOOL br; + + static const WCHAR not_existW[] = {'C',':','\\','n','o','t','_','e','x','i','s','t','.','w','a','v',0}; + static const WCHAR SystemAsteriskW[] = {'S','y','s','t','e','m','A','s','t','e','r','i','s','k',0}; + + br = sndPlaySoundA((LPCSTR)SND_ALIAS_SYSTEMASTERISK, SND_ALIAS_ID|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundW((LPCWSTR)SND_ALIAS_SYSTEMASTERISK, SND_ALIAS_ID|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundA((LPCSTR)sndAlias('X','Y'), SND_ALIAS_ID|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundW((LPCWSTR)sndAlias('X','Y'), SND_ALIAS_ID|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundA("SystemAsterisk", SND_ALIAS|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundW(SystemAsteriskW, SND_ALIAS|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundA("C:\not_exist.wav", SND_FILENAME|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); + + br = sndPlaySoundW(not_existW, SND_FILENAME|SND_SYNC); + ok(br == TRUE || br == FALSE, "sndPlaySound gave strange return: %u\n", br); +} + +static void test_fragmentsize(void) +{ + MMRESULT rc; + WAVEHDR hdr[2]; + HWAVEOUT wout; + WAVEFORMATEX fmt; + MMTIME mmtime; + DWORD wait; + HANDLE hevent; + + if(waveOutGetNumDevs() == 0) + return; + + fmt.wFormatTag = WAVE_FORMAT_PCM; + fmt.nChannels = 2; + fmt.nSamplesPerSec = 44100; + fmt.wBitsPerSample = 16; + fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; + fmt.nAvgBytesPerSec = fmt.nBlockAlign * fmt.nSamplesPerSec; + fmt.cbSize = sizeof(WAVEFORMATEX); + + hevent = CreateEventW(NULL, FALSE, FALSE, NULL); + g_tid = GetCurrentThreadId(); + + rc = waveOutOpen(&wout, WAVE_MAPPER, &fmt, (DWORD_PTR)callback_func, + (DWORD_PTR)hevent, CALLBACK_FUNCTION); + ok(rc == MMSYSERR_NOERROR || rc == WAVERR_BADFORMAT || + rc == MMSYSERR_INVALFLAG || rc == MMSYSERR_INVALPARAM, + "waveOutOpen(%s) failed: %s\n", dev_name(WAVE_MAPPER), wave_out_error(rc)); + if(rc != MMSYSERR_NOERROR){ + CloseHandle(hevent); + return; + } + + wait = WaitForSingleObject(hevent, 1000); + ok(wait == WAIT_OBJECT_0, "wave open callback missed\n"); + + memset(hdr, 0, sizeof(hdr)); + hdr[0].dwBufferLength = (fmt.nSamplesPerSec * fmt.nBlockAlign / 4) + 1; + hdr[1].dwBufferLength = hdr[0].dwBufferLength - 2; + hdr[1].lpData = hdr[0].lpData = + HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hdr[0].dwBufferLength); + + rc = waveOutPrepareHeader(wout, &hdr[0], sizeof(hdr[0])); + ok(rc == MMSYSERR_NOERROR, "waveOutPrepareHeader failed: %s\n", wave_out_error(rc)); + + rc = waveOutPrepareHeader(wout, &hdr[1], sizeof(hdr[1])); + ok(rc == MMSYSERR_NOERROR, "waveOutPrepareHeader failed: %s\n", wave_out_error(rc)); + + trace("writing %u bytes then %u bytes\n", hdr[0].dwBufferLength, hdr[1].dwBufferLength); + rc = waveOutWrite(wout, &hdr[0], sizeof(hdr[0])); + ok(rc == MMSYSERR_NOERROR, "waveOutWrite failed: %s\n", wave_out_error(rc)); + + rc = waveOutWrite(wout, &hdr[1], sizeof(hdr[1])); + ok(rc == MMSYSERR_NOERROR, "waveOutWrite failed: %s\n", wave_out_error(rc)); + + wait = WaitForSingleObject(hevent, 1000); + ok(wait == WAIT_OBJECT_0, "header 1 callback missed\n"); + + wait = WaitForSingleObject(hevent, 1000); + ok(wait == WAIT_OBJECT_0, "header 2 callback missed\n"); + + memset(&mmtime, 0, sizeof(mmtime)); + mmtime.wType = TIME_BYTES; + + rc = waveOutGetPosition(wout, &mmtime, sizeof(mmtime)); + ok(rc == MMSYSERR_NOERROR, "waveOutGetPosition failed: %s\n", wave_out_error(rc)); + + /* windows behavior is inconsistent */ + ok(mmtime.u.cb == 88200 || + mmtime.u.cb == 88196, "after position: %u\n", mmtime.u.cb); + + rc = waveOutClose(wout); + ok(rc == MMSYSERR_NOERROR, "waveOutClose failed: %s\n", wave_out_error(rc)); + + CloseHandle(hevent); +} + START_TEST(wave) { test_multiple_waveopens(); wave_out_tests(); + test_sndPlaySound(); + test_fragmentsize(); }