From 1a80d3e2e43e268e66cd79e506bb9b109f436ff0 Mon Sep 17 00:00:00 2001 From: =?utf-8?q?J=C3=B6rg=20H=C3=B6hle?= Date: Thu, 19 Aug 2010 16:05:12 +0200 Subject: [PATCH] winmm/tests: Initial tests for CDaudio with the MCI. --- dlls/winmm/tests/Makefile.in | 1 + dlls/winmm/tests/mcicda.c | 625 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 626 insertions(+), 0 deletions(-) create mode 100644 dlls/winmm/tests/mcicda.c diff --git a/dlls/winmm/tests/Makefile.in b/dlls/winmm/tests/Makefile.in index fa32299..e1c755d 100644 --- a/dlls/winmm/tests/Makefile.in +++ b/dlls/winmm/tests/Makefile.in @@ -9,6 +9,7 @@ C_SRCS = \ capture.c \ generated.c \ mci.c \ + mcicda.c \ midi.c \ mixer.c \ mmio.c \ diff --git a/dlls/winmm/tests/mcicda.c b/dlls/winmm/tests/mcicda.c new file mode 100644 index 0000000..14484a5 --- /dev/null +++ b/dlls/winmm/tests/mcicda.c @@ -0,0 +1,625 @@ +/* + * 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_PLAY_PARMS play; + MCI_SEEK_PARMS seek; + MCI_SAVE_PARMS save; + MCI_GENERIC_PARMS gen; + } MCI_PARMS_UNION; + +static const char* dbg_mcierr(MCIERROR err) +{ + switch (err) { + case 0: return "0=NOERROR"; +#define X(label) case label: return #label ; + X(MCIERR_INVALID_DEVICE_ID) + X(MCIERR_UNRECOGNIZED_KEYWORD) + X(MCIERR_UNRECOGNIZED_COMMAND) + X(MCIERR_HARDWARE) + X(MCIERR_INVALID_DEVICE_NAME) + X(MCIERR_OUT_OF_MEMORY) + X(MCIERR_DEVICE_OPEN) + X(MCIERR_CANNOT_LOAD_DRIVER) + X(MCIERR_MISSING_COMMAND_STRING) + X(MCIERR_PARAM_OVERFLOW) + X(MCIERR_MISSING_STRING_ARGUMENT) + X(MCIERR_BAD_INTEGER) + X(MCIERR_PARSER_INTERNAL) + X(MCIERR_DRIVER_INTERNAL) + X(MCIERR_MISSING_PARAMETER) + X(MCIERR_UNSUPPORTED_FUNCTION) + X(MCIERR_FILE_NOT_FOUND) + X(MCIERR_DEVICE_NOT_READY) + X(MCIERR_INTERNAL) + X(MCIERR_DRIVER) + X(MCIERR_CANNOT_USE_ALL) + X(MCIERR_MULTIPLE) + X(MCIERR_EXTENSION_NOT_FOUND) + X(MCIERR_OUTOFRANGE) + X(MCIERR_FLAGS_NOT_COMPATIBLE) + X(MCIERR_FILE_NOT_SAVED) + X(MCIERR_DEVICE_TYPE_REQUIRED) + X(MCIERR_DEVICE_LOCKED) + X(MCIERR_DUPLICATE_ALIAS) + X(MCIERR_BAD_CONSTANT) + X(MCIERR_MUST_USE_SHAREABLE) + X(MCIERR_MISSING_DEVICE_NAME) + X(MCIERR_BAD_TIME_FORMAT) + X(MCIERR_NO_CLOSING_QUOTE) + X(MCIERR_DUPLICATE_FLAGS) + X(MCIERR_INVALID_FILE) + X(MCIERR_NULL_PARAMETER_BLOCK) + X(MCIERR_UNNAMED_RESOURCE) + X(MCIERR_NEW_REQUIRES_ALIAS) + X(MCIERR_NOTIFY_ON_AUTO_OPEN) + X(MCIERR_NO_ELEMENT_ALLOWED) + X(MCIERR_NONAPPLICABLE_FUNCTION) + X(MCIERR_ILLEGAL_FOR_AUTO_OPEN) + X(MCIERR_FILENAME_REQUIRED) + X(MCIERR_EXTRA_CHARACTERS) + X(MCIERR_DEVICE_NOT_INSTALLED) + X(MCIERR_GET_CD) + X(MCIERR_SET_CD) + X(MCIERR_SET_DRIVE) + X(MCIERR_DEVICE_LENGTH) + X(MCIERR_DEVICE_ORD_LENGTH) + X(MCIERR_NO_INTEGER) + X(MCIERR_NO_WINDOW) + X(MCIERR_CREATEWINDOW) + X(MCIERR_FILE_READ) + X(MCIERR_FILE_WRITE) + X(MCIERR_NO_IDENTITY) +#undef X + default: { + static char name[20]; /* Not to be called twice in a parameter list! */ + sprintf(name, "MMSYSERR %u", err); + return name; + } + } +} + +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 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); +} + +/* TODO test_open "open X: type cdaudio" etc. */ +/* TODO demonstrate open X:\ fails on win95 while open X: works. */ +/* 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 = mciSendString("open cdaudio alias c notify shareable", buf, sizeof(buf), hwnd); + if (err == MCIERR_INVALID_DEVICE_ID) /* Wine special */ todo_wine + ok(!err, "open cdaudio must succeed even without CD-ROM drive\n"); else + ok(!err || broken(err == MCIERR_MUST_USE_SHAREABLE), + "mci open cdaudio notify returned %s\n", dbg_mcierr(err)); + test_notification(hwnd, "open alias notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL); + /* Some machines return MUST_USE_SHAREABLE when there's trouble with the hardware + * (e.g. unreadable disk), 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); + + err = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendCommand(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 = mciSendString("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(ok_hw)); + return; + } + + /* Linux leaves the drive at some random position, + * native initialises to the start position below. */ + err = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendString("play c from 00:02:00 to 00:01:00 notify", buf, sizeof(buf), hwnd); + todo_wine 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 = mciSendString("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 = mciSendString("seek c wait", buf, sizeof(buf), hwnd); + ok(err == MCIERR_MISSING_PARAMETER, "seek noflag: %s\n", dbg_mcierr(err)); + + err = mciSendString("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 = mciSendString("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 = mciSendString("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 + * 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? */ + err = mciSendString("set c video audio all on", buf, sizeof(buf), hwnd); + ok(!err, "set video/audio: %s\n", dbg_mcierr(err)); + + err = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendString("status c notify wait", buf, sizeof(buf), hwnd); + ok(err == MCIERR_MISSING_PARAMETER, "status noflag: %s\n", dbg_mcierr(err)); + + err = mciSendString("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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendCommand(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 = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_TO, (DWORD_PTR)&parm); + ok(err == MCIERR_OUTOFRANGE, "PLAY: %s\n", dbg_mcierr(err)); + + err = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendCommand(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 = mciSendString("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 = mciSendString("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 = mciSendString("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 = mciSendCommand(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 = mciSendCommand(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 = mciSendString("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 = mciSendCommand(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); +} + +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); + err = mciSendCommand(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 = mciSendString("close all", NULL, 0, hwnd); + ok(!err, "final close all returned %s\n", dbg_mcierr(err)); + test_notification(hwnd, "-tests complete-", 0); + DestroyWindow(hwnd); +} -- 1.5.6.3