2 * Test MCI CD-ROM access
4 * Copyright 2010 Jörg Höhle
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 #include "wine/test.h"
27 MCI_STATUS_PARMS status;
28 MCI_GETDEVCAPS_PARMS caps;
32 MCI_GENERIC_PARMS gen;
35 static const char* dbg_mcierr(MCIERROR err)
38 case 0: return "0=NOERROR";
39 #define X(label) case label: return #label ;
40 X(MCIERR_INVALID_DEVICE_ID)
41 X(MCIERR_UNRECOGNIZED_KEYWORD)
42 X(MCIERR_UNRECOGNIZED_COMMAND)
44 X(MCIERR_INVALID_DEVICE_NAME)
45 X(MCIERR_OUT_OF_MEMORY)
47 X(MCIERR_CANNOT_LOAD_DRIVER)
48 X(MCIERR_MISSING_COMMAND_STRING)
49 X(MCIERR_PARAM_OVERFLOW)
50 X(MCIERR_MISSING_STRING_ARGUMENT)
52 X(MCIERR_PARSER_INTERNAL)
53 X(MCIERR_DRIVER_INTERNAL)
54 X(MCIERR_MISSING_PARAMETER)
55 X(MCIERR_UNSUPPORTED_FUNCTION)
56 X(MCIERR_FILE_NOT_FOUND)
57 X(MCIERR_DEVICE_NOT_READY)
60 X(MCIERR_CANNOT_USE_ALL)
62 X(MCIERR_EXTENSION_NOT_FOUND)
64 X(MCIERR_FLAGS_NOT_COMPATIBLE)
65 X(MCIERR_FILE_NOT_SAVED)
66 X(MCIERR_DEVICE_TYPE_REQUIRED)
67 X(MCIERR_DEVICE_LOCKED)
68 X(MCIERR_DUPLICATE_ALIAS)
69 X(MCIERR_BAD_CONSTANT)
70 X(MCIERR_MUST_USE_SHAREABLE)
71 X(MCIERR_MISSING_DEVICE_NAME)
72 X(MCIERR_BAD_TIME_FORMAT)
73 X(MCIERR_NO_CLOSING_QUOTE)
74 X(MCIERR_DUPLICATE_FLAGS)
75 X(MCIERR_INVALID_FILE)
76 X(MCIERR_NULL_PARAMETER_BLOCK)
77 X(MCIERR_UNNAMED_RESOURCE)
78 X(MCIERR_NEW_REQUIRES_ALIAS)
79 X(MCIERR_NOTIFY_ON_AUTO_OPEN)
80 X(MCIERR_NO_ELEMENT_ALLOWED)
81 X(MCIERR_NONAPPLICABLE_FUNCTION)
82 X(MCIERR_ILLEGAL_FOR_AUTO_OPEN)
83 X(MCIERR_FILENAME_REQUIRED)
84 X(MCIERR_EXTRA_CHARACTERS)
85 X(MCIERR_DEVICE_NOT_INSTALLED)
89 X(MCIERR_DEVICE_LENGTH)
90 X(MCIERR_DEVICE_ORD_LENGTH)
93 X(MCIERR_CREATEWINDOW)
99 static char name[20]; /* Not to be called twice in a parameter list! */
100 sprintf(name, "MMSYSERR %u", err);
106 static BOOL spurious_message(LPMSG msg)
108 /* WM_DEVICECHANGE 0x0219 appears randomly */
109 if(msg->message != MM_MCINOTIFY) {
110 trace("skipping spurious message %04x\n",msg->message);
116 /* A single ok() in each code path allows to prefix this with todo_wine */
117 #define test_notification(hwnd, command, type) test_notification_dbg(hwnd, command, type, __LINE__)
118 static void test_notification_dbg(HWND hwnd, const char* command, WPARAM type, int line)
119 { /* Use type 0 as meaning no message */
122 do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); }
123 while(seen && spurious_message(&msg));
125 /* We observe transient delayed notification, mostly on native.
126 * Notification is not always present right when mciSend returns. */
127 trace_(__FILE__,line)("Waiting for delayed notification from %s\n", command);
128 MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE);
129 seen = PeekMessageA(&msg, hwnd, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE);
132 ok_(__FILE__,line)(type==0, "Expect message %04lx from %s\n", type, command);
133 else if(msg.hwnd != hwnd)
134 ok_(__FILE__,line)(msg.hwnd == hwnd, "Didn't get the handle to our test window\n");
135 else if(msg.message != MM_MCINOTIFY)
136 ok_(__FILE__,line)(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command);
137 else ok_(__FILE__,line)(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command);
140 #define CDFRAMES_PERSEC 75
141 static DWORD MSF_Add(DWORD d1, DWORD d2)
144 f = MCI_MSF_FRAME(d1) + MCI_MSF_FRAME(d2);
145 c = f / CDFRAMES_PERSEC;
146 f = f % CDFRAMES_PERSEC;
147 s = MCI_MSF_SECOND(d1) + MCI_MSF_SECOND(d2) + c;
150 m = MCI_MSF_MINUTE(d1) + MCI_MSF_MINUTE(d2) + c; /* may be > 60 */
151 return MCI_MAKE_MSF(m,s,f);
154 /* TODO test_open "open X: type cdaudio" etc. */
155 /* TODO demonstrate open X:\ fails on win95 while open X: works. */
156 /* TODO show that shareable flag is not what Wine implements. */
158 static void test_play(HWND hwnd)
160 MCIDEVICEID wDeviceID;
161 MCI_PARMS_UNION parm;
163 DWORD numtracks, track, duration;
164 DWORD factor = winetest_interactive ? 3 : 1;
166 memset(buf, 0, sizeof(buf));
167 parm.gen.dwCallback = (DWORD_PTR)hwnd; /* once to rule them all */
169 err = mciSendString("open cdaudio alias c notify shareable", buf, sizeof(buf), hwnd);
170 if (err == MCIERR_INVALID_DEVICE_ID) /* Wine special */ todo_wine
171 ok(!err, "open cdaudio must succeed even without CD-ROM drive\n"); else
172 ok(!err || broken(err == MCIERR_MUST_USE_SHAREABLE),
173 "mci open cdaudio notify returned %s\n", dbg_mcierr(err));
174 test_notification(hwnd, "open alias notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
175 /* Some machines return MUST_USE_SHAREABLE when there's trouble with the hardware
176 * (e.g. unreadable disk), yet adding that flag does not help get past this error. */
179 skip("Cannot open any cdaudio device, %s.\n", dbg_mcierr(err));
182 wDeviceID = atoi(buf);
183 ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf);
185 err = mciSendString("capability c has video notify", buf, sizeof(buf), hwnd);
186 ok(!err, "capability video: %s\n", dbg_mcierr(err));
187 if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf);
188 test_notification(hwnd, "capability notify", MCI_NOTIFY_SUCCESSFUL);
190 err = mciSendString("capability c can play", buf, sizeof(buf), hwnd);
191 ok(!err, "capability video: %s\n", dbg_mcierr(err));
192 if(!err) ok(!strcmp(buf, "true"), "capability play is %s\n", buf);
194 err = mciSendString("capability c", buf, sizeof(buf), NULL);
195 ok(err == MCIERR_MISSING_PARAMETER, "capability nokeyword: %s\n", dbg_mcierr(err));
197 parm.caps.dwItem = 0x4001;
198 parm.caps.dwReturn = 0xFEEDABAD;
199 err = mciSendCommand(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
200 ok(err == MCIERR_UNSUPPORTED_FUNCTION, "GETDEVCAPS %x: %s\n", parm.caps.dwItem, dbg_mcierr(err));
202 parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE;
203 err = mciSendCommand(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
204 ok(!err, "GETDEVCAPS device type: %s\n", dbg_mcierr(err));
205 if(!err) ok( parm.caps.dwReturn == MCI_DEVTYPE_CD_AUDIO, "getdevcaps device type: %u\n", parm.caps.dwReturn);
207 err = mciSendCommand(wDeviceID, MCI_RECORD, 0, (DWORD_PTR)&parm);
208 ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_RECORD: %s\n", dbg_mcierr(err));
210 /* Wine's MCI_MapMsgAtoW crashes on MCI_SAVE without parm->lpfilename */
211 parm.save.lpfilename = "foo";
212 err = mciSendCommand(wDeviceID, MCI_SAVE, 0, (DWORD_PTR)&parm);
213 ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_SAVE: %s\n", dbg_mcierr(err));
215 /* commands from the core set are UNSUPPORTED, others UNRECOGNIZED */
216 err = mciSendCommand(wDeviceID, MCI_STEP, 0, (DWORD_PTR)&parm);
217 ok(err == MCIERR_UNRECOGNIZED_COMMAND, "MCI_STEP: %s\n", dbg_mcierr(err));
219 parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
220 parm.status.dwReturn = 0xFEEDABAD;
221 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
222 ok(!err, "STATUS time format: %s\n", dbg_mcierr(err));
223 if(!err) ok(parm.status.dwReturn == MCI_FORMAT_MSF, "status time default format: %ld\n", parm.status.dwReturn);
226 err = mciSendString("info c product wait notify", buf, sizeof(buf), hwnd);
227 ok(!err, "info product: %s\n", dbg_mcierr(err));
228 test_notification(hwnd, "info notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
230 parm.status.dwItem = MCI_STATUS_MEDIA_PRESENT;
231 parm.status.dwReturn = 0xFEEDABAD;
232 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
233 ok(err || parm.status.dwReturn == TRUE || parm.status.dwReturn == FALSE,
234 "STATUS media present: %s\n", dbg_mcierr(err));
236 if (parm.status.dwReturn != TRUE) {
237 skip("No CD-ROM in drive.\n");
241 parm.status.dwItem = MCI_STATUS_MODE;
242 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
243 ok(!err, "STATUS mode: %s\n", dbg_mcierr(err));
244 switch(parm.status.dwReturn) {
245 case MCI_MODE_NOT_READY:
246 skip("CD-ROM mode not ready (DVD in drive?)\n");
248 case MCI_MODE_OPEN: /* should not happen with MEDIA_PRESENT */
249 skip("CD-ROM drive is open\n");
250 /* set door closed may not work. */
252 default: /* play/record/seek/pause */
253 ok(parm.status.dwReturn==MCI_MODE_STOP, "STATUS mode is %lx\n", parm.status.dwReturn);
255 case MCI_MODE_STOP: /* normal */
259 /* Initial mode is "stopped" with a CD in drive */
260 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
261 ok(!err, "status mode: %s\n", dbg_mcierr(err));
262 if(!err) ok(!strcmp(buf, "stopped"), "status mode is initially %s\n", buf);
264 err = mciSendString("status c ready", buf, sizeof(buf), hwnd);
265 ok(!err, "status ready: %s\n", dbg_mcierr(err));
266 if(!err) ok(!strcmp(buf, "true"), "status ready with media is %s\n", buf);
268 err = mciSendString("info c product identity", buf, sizeof(buf), hwnd);
269 ok(!err, "info 2flags: %s\n", dbg_mcierr(err)); /* not MCIERR_FLAGS_NOT_COMPATIBLE */
270 /* Precedence rule p>u>i verified experimentally, not tested here. */
272 err = mciSendString("info c identity", buf, sizeof(buf), hwnd);
273 ok(!err || err == MCIERR_HARDWARE, "info identity: %s\n", dbg_mcierr(err));
274 /* a blank disk causes MCIERR_HARDWARE and other commands to fail likewise. */
277 err = mciSendString("info c upc", buf, sizeof(buf), hwnd);
278 ok(err == ok_hw || err == MCIERR_NO_IDENTITY, "info upc: %s\n", dbg_mcierr(err));
280 parm.status.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
281 parm.status.dwReturn = 0;
282 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
283 ok(err == ok_hw, "STATUS number of tracks: %s\n", dbg_mcierr(err));
284 numtracks = parm.status.dwReturn;
285 /* cf. MAXIMUM_NUMBER_TRACKS */
286 ok(0 < numtracks && numtracks <= 99, "number of tracks=%ld\n", parm.status.dwReturn);
288 err = mciSendString("status c length", buf, sizeof(buf), hwnd);
289 ok(err == ok_hw, "status length: %s\n", dbg_mcierr(err));
290 if(!err) trace("CD length %s\n", buf);
292 if(err) { /* MCIERR_HARDWARE when given a blank disk */
293 skip("status length %s (blank disk?)\n", dbg_mcierr(ok_hw));
297 /* Linux leaves the drive at some random position,
298 * native initialises to the start position below. */
299 err = mciSendString("status c position", buf, sizeof(buf), hwnd);
300 ok(!err, "status position: %s\n", dbg_mcierr(err));
301 if(!err) todo_wine ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
302 "status position initially %s\n", buf);
303 /* 2 seconds is the initial position even with data tracks. */
305 err = mciSendString("status c position start notify", buf, sizeof(buf), hwnd);
306 ok(err == ok_hw, "status position start: %s\n", dbg_mcierr(err));
307 if(!err) ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
308 "status position start %s\n", buf);
309 test_notification(hwnd, "status notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
311 err = mciSendString("status c position start track 1 notify", buf, sizeof(buf), hwnd);
312 ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start: %s\n", dbg_mcierr(err));
313 test_notification(hwnd, "status 2flags", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
315 err = mciSendString("play c from 00:02:00 to 00:01:00 notify", buf, sizeof(buf), hwnd);
316 todo_wine ok(err == MCIERR_OUTOFRANGE, "play 2s to 1s: %s\n", dbg_mcierr(err));
317 test_notification(hwnd, "play 2s to 1s", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
319 err = mciSendString("resume c", buf, sizeof(buf), hwnd);
320 ok(err == MCIERR_HARDWARE || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
321 "resume without play: %s\n", dbg_mcierr(err)); /* not NONAPPLICABLE_FUNCTION */
322 /* vmware with a .iso (data-only) yields no error on NT/w2k */
324 err = mciSendString("seek c wait", buf, sizeof(buf), hwnd);
325 ok(err == MCIERR_MISSING_PARAMETER, "seek noflag: %s\n", dbg_mcierr(err));
327 err = mciSendString("seek c to start to end", buf, sizeof(buf), hwnd);
328 ok(err == MCIERR_FLAGS_NOT_COMPATIBLE || broken(!err), "seek to start+end: %s\n", dbg_mcierr(err));
329 /* Win9x only errors out with Seek to start to <position> */
331 /* set Wine to a defined position before play */
332 err = mciSendString("seek c to start notify", buf, sizeof(buf), hwnd);
333 ok(!err, "seek to start: %s\n", dbg_mcierr(err));
334 test_notification(hwnd, "seek to start", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
335 /* Win9X Status position / current track then sometimes report the end position / track! */
337 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
338 ok(!err, "status mode: %s\n", dbg_mcierr(err));
339 if(!err) ok(!strcmp(buf, "stopped"), "status mode after seek is %s\n", buf);
341 /* MCICDA ignores MCI_SET_VIDEO
342 * One xp machine ignored SET_AUDIO, one w2k and one w7 machine honoured it
343 * and simultaneously toggled the mute button in the mixer control panel.
344 * Or does it only depend on the HW, not the OS? */
345 err = mciSendString("set c video audio all on", buf, sizeof(buf), hwnd);
346 ok(!err, "set video/audio: %s\n", dbg_mcierr(err));
348 err = mciSendString("set c time format ms", buf, sizeof(buf), hwnd);
349 ok(!err, "set time format ms: %s\n", dbg_mcierr(err));
351 memset(buf, 0, sizeof(buf));
352 err = mciSendString("status c position start", buf, sizeof(buf), hwnd);
353 ok(!err, "status position start (ms): %s\n", dbg_mcierr(err));
354 duration = atoi(buf);
355 if(!err) ok(duration > 2000, "status position initially %sms\n", buf);
356 /* 00:02:00 corresponds to 2001 ms, 02:33 -> 2441 etc. */
358 err = mciSendString("status c position start track 1", buf, sizeof(buf), hwnd);
359 ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start+track: %s\n", dbg_mcierr(err));
361 err = mciSendString("status c notify wait", buf, sizeof(buf), hwnd);
362 ok(err == MCIERR_MISSING_PARAMETER, "status noflag: %s\n", dbg_mcierr(err));
364 err = mciSendString("status c length track 1", buf, sizeof(buf), hwnd);
365 ok(!err, "status length (ms): %s\n", dbg_mcierr(err));
367 trace("track #1 length %sms\n", buf);
368 duration = atoi(buf);
369 } else duration = 2001; /* for the position test below */
371 if (0) { /* causes some native systems to return Seek and Play with MCIERR_HARDWARE */
372 /* depending on capability can eject only? */
373 err = mciSendString("set c door closed notify", buf, sizeof(buf), hwnd);
374 ok(!err, "set door closed: %s\n", dbg_mcierr(err));
375 test_notification(hwnd, "door closed", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
377 /* Changing the disk while the MCI device is open causes the Status
378 * command to report stale data. Native obviously caches the TOC. */
380 /* status type track is localised, strcmp("audio|other") may fail. */
381 parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
382 parm.status.dwTrack = 1;
383 parm.status.dwReturn = 0xFEEDABAD;
384 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
385 ok(!err, "STATUS type track 1: %s\n", dbg_mcierr(err));
386 ok(parm.status.dwReturn==MCI_CDA_TRACK_OTHER || parm.status.dwReturn==MCI_CDA_TRACK_AUDIO,
387 "unknown track type %lx\n", parm.status.dwReturn);
389 if (parm.status.dwReturn == MCI_CDA_TRACK_OTHER) {
390 /* Find an audio track */
391 parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
392 parm.status.dwTrack = numtracks;
393 parm.status.dwReturn = 0xFEEDABAD;
394 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
395 ok(!err, "STATUS type track %u: %s\n", numtracks, dbg_mcierr(err));
396 ok(parm.status.dwReturn == MCI_CDA_TRACK_OTHER || parm.status.dwReturn == MCI_CDA_TRACK_AUDIO,
397 "unknown track type %lx\n", parm.status.dwReturn);
398 track = (!err && parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) ? numtracks : 0;
400 /* Seek to start (above) skips over data tracks
401 * In case of a data only CD, it seeks to the end of disk, however
402 * another Status position a few seconds later yields MCIERR_HARDWARE. */
403 parm.status.dwItem = MCI_STATUS_POSITION;
404 parm.status.dwReturn = 2000;
405 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
406 ok(!err || broken(err == MCIERR_HARDWARE), "STATUS position: %s\n", dbg_mcierr(err));
408 if(!err && track) ok(parm.status.dwReturn > duration,
409 "Seek did not skip data tracks, position %lums\n", parm.status.dwReturn);
410 /* dwReturn > start + length(#1) may fail because of small position report fluctuation.
411 * On some native systems, status position fluctuates around the target position;
412 * Successive calls return varying positions! */
414 err = mciSendString("set c time format msf", buf, sizeof(buf), hwnd);
415 ok(!err, "set time format msf: %s\n", dbg_mcierr(err));
417 parm.status.dwItem = MCI_STATUS_LENGTH;
418 parm.status.dwTrack = 1;
419 parm.status.dwReturn = 0xFEEDABAD;
420 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
421 ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
422 duration = parm.status.dwReturn;
423 trace("track #1 length: %02um:%02us:%02uframes\n",
424 MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
425 ok(duration>>24==0, "CD length high bits %08X\n", duration);
427 /* TODO only with mixed CDs? */
428 /* play track 1 to length silently works with data tracks */
429 parm.play.dwFrom = MCI_MAKE_MSF(0,2,0);
430 parm.play.dwTo = duration; /* omitting 2 seconds from end */
431 err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_TO, (DWORD_PTR)&parm);
432 ok(!err, "PLAY data to %08X: %s\n", duration, dbg_mcierr(err));
434 Sleep(1500*factor); /* Time to spin up, hopefully less than track length */
436 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
437 ok(!err, "status mode: %s\n", dbg_mcierr(err));
438 if(!err) ok(!strcmp(buf, "stopped"), "status mode on data is %s\n", buf);
439 } else if (parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) {
440 skip("Got no mixed data+audio CD.\n");
445 skip("Found no audio track.\n");
449 err = mciSendString("set c time format msf", buf, sizeof(buf), hwnd);
450 ok(!err, "set time format msf: %s\n", dbg_mcierr(err));
452 parm.status.dwItem = MCI_STATUS_LENGTH;
453 parm.status.dwTrack = numtracks;
454 parm.status.dwReturn = 0xFEEDABAD;
455 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
456 ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
457 duration = parm.status.dwReturn;
458 trace("last track length: %02um:%02us:%02uframes\n",
459 MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
460 ok(duration>>24==0, "CD length high bits %08X\n", duration);
462 parm.status.dwItem = MCI_STATUS_POSITION;
463 /* dwTrack is still set */
464 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
465 ok(!err, "STATUS position start track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
466 trace("last track position: %02um:%02us:%02uframes\n",
467 MCI_MSF_MINUTE(parm.status.dwReturn), MCI_MSF_SECOND(parm.status.dwReturn), MCI_MSF_FRAME(parm.status.dwReturn));
469 /* Seek to position + length always works, esp.
470 * for the last track it's NOT the position of the lead-out. */
471 parm.seek.dwTo = MSF_Add(parm.status.dwReturn, duration);
472 err = mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
473 ok(!err, "SEEK to %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err));
475 parm.seek.dwTo = MSF_Add(parm.seek.dwTo, MCI_MAKE_MSF(0,0,1));
476 err = mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
477 ok(err == MCIERR_OUTOFRANGE, "SEEK past %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err));
479 err = mciSendString("set c time format tmsf", buf, sizeof(buf), hwnd);
480 ok(!err, "set time format tmsf: %s\n", dbg_mcierr(err));
482 parm.play.dwFrom = track;
483 err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_NOTIFY, (DWORD_PTR)&parm);
484 ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
487 skip("Cannot manage to play track %u.\n", track);
491 Sleep(1800*factor); /* Time to spin up, hopefully less than track length */
493 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
494 ok(!err, "status mode: %s\n", dbg_mcierr(err));
495 if(!err) ok(!strcmp(buf, "playing"), "status mode during play is %s\n", buf);
497 err = mciSendString("pause c", buf, sizeof(buf), hwnd);
498 ok(!err, "pause: %s\n", dbg_mcierr(err));
500 test_notification(hwnd, "pause should abort notification", MCI_NOTIFY_ABORTED);
502 /* Native returns stopped when paused,
503 * yet the Stop command is different as it would disallow Resume. */
504 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
505 ok(!err, "status mode: %s\n", dbg_mcierr(err));
506 if(!err) todo_wine ok(!strcmp(buf, "stopped"), "status mode while paused is %s\n", buf);
508 err = mciSendCommand(wDeviceID, MCI_RESUME, 0, 0);
509 ok(!err || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
510 "RESUME without parms: %s\n", dbg_mcierr(err));
514 /* Native continues to play without interruption */
515 err = mciSendCommand(wDeviceID, MCI_PLAY, 0, 0);
516 todo_wine ok(!err, "PLAY without parms: %s\n", dbg_mcierr(err));
518 parm.play.dwFrom = MCI_MAKE_TMSF(numtracks,0,1,0);
520 err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_TO, (DWORD_PTR)&parm);
521 ok(err == MCIERR_OUTOFRANGE, "PLAY: %s\n", dbg_mcierr(err));
523 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
524 ok(!err, "status mode: %s\n", dbg_mcierr(err));
525 if(!err) ok(!strcmp(buf, "playing"), "status mode after play is %s\n", buf);
527 err = mciSendCommand(wDeviceID, MCI_STOP, MCI_NOTIFY, (DWORD_PTR)&parm);
528 ok(!err, "STOP notify: %s\n", dbg_mcierr(err));
529 test_notification(hwnd, "STOP notify", MCI_NOTIFY_SUCCESSFUL);
530 test_notification(hwnd, "STOP #1", 0);
532 parm.play.dwFrom = track;
533 err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_NOTIFY, (DWORD_PTR)&parm);
534 ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
538 parm.seek.dwTo = 1; /* not <track>, to test position below */
539 err = mciSendCommand(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
540 ok(!err, "SEEK to %u notify: %s\n", track, dbg_mcierr(err));
541 /* Note that native's Status position / current track may move the head
542 * and reflect the new position only seconds after issuing the command. */
545 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
546 ok(!err, "status mode: %s\n", dbg_mcierr(err));
547 if(!err) ok(!strcmp(buf, "stopped"), "status mode after play is %s\n", buf);
549 test_notification(hwnd, "Seek aborts Play", MCI_NOTIFY_ABORTED);
550 test_notification(hwnd, "Seek", 0);
552 parm.play.dwFrom = track;
553 parm.play.dwTo = MCI_MAKE_TMSF(track,0,0,21); /* 21 frames, subsecond */
554 err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY, (DWORD_PTR)&parm);
555 ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
559 err = mciSendString("status c mode", buf, sizeof(buf), hwnd);
560 ok(!err, "status mode: %s\n", dbg_mcierr(err));
561 if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf);
562 if(!err && !strcmp(buf, "playing")) trace("status playing after sleep\n");
564 /* Playing to end asynchronously sends no notification! */
565 test_notification(hwnd, "PLAY to end", 0);
567 err = mciSendString("status c mode notify", buf, sizeof(buf), hwnd);
568 ok(!err, "status mode: %s\n", dbg_mcierr(err));
569 if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf);
570 if(!err && !strcmp(buf, "playing")) trace("status still playing\n");
571 /* Some systems report playing even after Sleep(3900ms) yet the successful
572 * notification tests (not ABORTED) indicates they are finished. */
574 test_notification(hwnd, "dangling from PLAY", MCI_NOTIFY_SUPERSEDED);
575 test_notification(hwnd, "status mode", MCI_NOTIFY_SUCCESSFUL);
577 err = mciSendString("stop c", buf, sizeof(buf), hwnd);
578 ok(!err, "stop: %s\n", dbg_mcierr(err));
580 test_notification(hwnd, "PLAY to end", 0);
582 /* length as MSF despite set time format TMSF */
583 parm.status.dwItem = MCI_STATUS_LENGTH;
584 parm.status.dwTrack = numtracks;
585 parm.status.dwReturn = 0xFEEDABAD;
586 err = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM|MCI_TRACK, (DWORD_PTR)&parm);
587 ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
588 ok(duration == parm.status.dwReturn, "length MSF<>TMSF %08lX\n", parm.status.dwReturn);
590 /* Play from position start to start+length always works. */
591 /* TODO? also play it using MSF */
592 parm.play.dwFrom = numtracks;
593 parm.play.dwTo = (duration << 8) | numtracks; /* as TMSF */
594 err = mciSendCommand(wDeviceID, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY, (DWORD_PTR)&parm);
595 ok(!err, "PLAY (TMSF) from %08X to %08X: %s\n", parm.play.dwFrom, parm.play.dwTo, dbg_mcierr(err));
599 err = mciSendString("status c current track", buf, sizeof(buf), hwnd);
600 ok(!err, "status track: %s\n", dbg_mcierr(err));
601 if(!err) todo_wine ok(numtracks == atoi(buf), "status current track gave %s, expected %u\n", buf, numtracks);
602 /* fails in Wine because SEEK is independent on IOCTL_CDROM_RAW_READ */
604 err = mciSendCommand(wDeviceID, MCI_STOP, 0, 0);
605 ok(!err, "STOP: %s\n", dbg_mcierr(err));
606 test_notification(hwnd, "STOP aborts", MCI_NOTIFY_ABORTED);
607 test_notification(hwnd, "STOP final", 0);
614 hwnd = CreateWindowExA(0, "static", "mcicda test", WS_POPUP, 0,0,100,100,
616 test_notification(hwnd, "-prior to tests-", 0);
618 err = mciSendCommand(MCI_ALL_DEVICE_ID, MCI_STOP, 0, 0);
619 todo_wine ok(!err || broken(err == MCIERR_HARDWARE /* blank CD or testbot without CD-ROM */),
620 "STOP all returned %s\n", dbg_mcierr(err));
621 err = mciSendString("close all", NULL, 0, hwnd);
622 ok(!err, "final close all returned %s\n", dbg_mcierr(err));
623 test_notification(hwnd, "-tests complete-", 0);