1 /*
2  * Test MCI CD-ROM access
3  *
4  * Copyright 2010 Jörg Höhle
5  *
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.
10  *
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.
15  *
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
19  */
20 
21 #include <stdio.h>
22 #include "windows.h"
23 #include "mmsystem.h"
24 #include "wine/test.h"
25 
26 typedef union {
27       MCI_STATUS_PARMS     status;
28       MCI_GETDEVCAPS_PARMS caps;
29       MCI_OPEN_PARMSA      open;
30       MCI_PLAY_PARMS       play;
31       MCI_SEEK_PARMS       seek;
32       MCI_SAVE_PARMSA      save;
33       MCI_GENERIC_PARMS    gen;
34     } MCI_PARMS_UNION;
35 
36 extern const char* dbg_mcierr(MCIERROR err); /* from mci.c */
37 
38 static BOOL spurious_message(LPMSG msg)
39 {
40   /* WM_DEVICECHANGE 0x0219 appears randomly */
41   if(msg->message != MM_MCINOTIFY) {
42     trace("skipping spurious message %04x\n",msg->message);
43     return TRUE;
44   }
45   return FALSE;
46 }
47 
48 /* A single ok() in each code path allows us to prefix this with todo_wine */
49 #define test_notification(hwnd, command, type) test_notification_dbg(hwnd, command, type, __LINE__)
50 static void test_notification_dbg(HWND hwnd, const char* command, WPARAM type, int line)
51 {   /* Use type 0 as meaning no message */
52     MSG msg;
53     BOOL seen;
54     do { seen = PeekMessageA(&msg, hwnd, 0, 0, PM_REMOVE); }
55     while(seen && spurious_message(&msg));
56     if(type && !seen) {
57       /* We observe transient delayed notification, mostly on native.
58        * Notification is not always present right when mciSend returns. */
59       trace_(__FILE__,line)("Waiting for delayed notification from %s\n", command);
60       MsgWaitForMultipleObjects(0, NULL, FALSE, 3000, QS_POSTMESSAGE);
61       seen = PeekMessageA(&msg, hwnd, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE);
62     }
63     if(!seen)
64       ok_(__FILE__,line)(type==0, "Expect message %04lx from %s\n", type, command);
65     else if(msg.hwnd != hwnd)
66         ok_(__FILE__,line)(msg.hwnd == hwnd, "Didn't get the handle to our test window\n");
67     else if(msg.message != MM_MCINOTIFY)
68         ok_(__FILE__,line)(msg.message == MM_MCINOTIFY, "got %04x instead of MM_MCINOTIFY from command %s\n", msg.message, command);
69     else ok_(__FILE__,line)(msg.wParam == type, "got %04lx instead of MCI_NOTIFY_xyz %04lx from command %s\n", msg.wParam, type, command);
70 }
71 
72 #define CDFRAMES_PERSEC                 75
73 static DWORD MSF_Add(DWORD d1, DWORD d2)
74 {
75     WORD c, m, s, f;
76     f = MCI_MSF_FRAME(d1)  + MCI_MSF_FRAME(d2);
77     c = f / CDFRAMES_PERSEC;
78     f = f % CDFRAMES_PERSEC;
79     s = MCI_MSF_SECOND(d1) + MCI_MSF_SECOND(d2) + c;
80     c = s / 60;
81     s = s % 60;
82     m = MCI_MSF_MINUTE(d1) + MCI_MSF_MINUTE(d2) + c; /* may be > 60 */
83     return MCI_MAKE_MSF(m,s,f);
84 }
85 
86 static MCIERROR ok_open = 0; /* MCIERR_CANNOT_LOAD_DRIVER */
87 
88 /* TODO show that shareable flag is not what Wine implements. */
89 
90 static void test_play(HWND hwnd)
91 {
92     MCIDEVICEID wDeviceID;
93     MCI_PARMS_UNION parm;
94     MCIERROR err, ok_hw;
95     DWORD numtracks, track, duration;
96     DWORD factor = winetest_interactive ? 3 : 1;
97     char buf[1024];
98     memset(buf, 0, sizeof(buf));
99     parm.gen.dwCallback = (DWORD_PTR)hwnd; /* once to rule them all */
100 
101     err = mciSendStringA("open cdaudio alias c notify shareable", buf, sizeof(buf), hwnd);
102     ok(!err || err == MCIERR_CANNOT_LOAD_DRIVER || err == MCIERR_MUST_USE_SHAREABLE,
103        "mci open cdaudio notify returned %s\n", dbg_mcierr(err));
104     ok_open = err;
105     test_notification(hwnd, "open alias notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
106     /* Native returns MUST_USE_SHAREABLE when there's trouble with the hardware
107      * (e.g. unreadable disk) or when Media Player already has the device open,
108      * yet adding that flag does not help get past this error. */
109 
110     if(err) {
111         skip("Cannot open any cdaudio device, %s.\n", dbg_mcierr(err));
112         return;
113     }
114     wDeviceID = atoi(buf);
115     ok(!strcmp(buf,"1"), "mci open deviceId: %s, expected 1\n", buf);
116     /* Win9X-ME may start the MCI and media player upon insertion of a CD. */
117 
118     err = mciSendStringA("sysinfo all name 1 open", buf, sizeof(buf), NULL);
119     ok(!err,"sysinfo all name 1 returned %s\n", dbg_mcierr(err));
120     if(!err && wDeviceID != 1) trace("Device '%s' is open.\n", buf);
121 
122     err = mciSendStringA("capability c has video notify", buf, sizeof(buf), hwnd);
123     ok(!err, "capability video: %s\n", dbg_mcierr(err));
124     if(!err) ok(!strcmp(buf, "false"), "capability video is %s\n", buf);
125     test_notification(hwnd, "capability notify", MCI_NOTIFY_SUCCESSFUL);
126 
127     err = mciSendStringA("capability c can play", buf, sizeof(buf), hwnd);
128     ok(!err, "capability video: %s\n", dbg_mcierr(err));
129     if(!err) ok(!strcmp(buf, "true"), "capability play is %s\n", buf);
130 
131     err = mciSendStringA("capability c", buf, sizeof(buf), NULL);
132     ok(err == MCIERR_MISSING_PARAMETER, "capability nokeyword: %s\n", dbg_mcierr(err));
133 
134     parm.caps.dwItem = 0x4001;
135     parm.caps.dwReturn = 0xFEEDABAD;
136     err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
137     ok(err == MCIERR_UNSUPPORTED_FUNCTION, "GETDEVCAPS %x: %s\n", parm.caps.dwItem, dbg_mcierr(err));
138 
139     parm.caps.dwItem = MCI_GETDEVCAPS_DEVICE_TYPE;
140     err = mciSendCommandA(wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM, (DWORD_PTR)&parm);
141     ok(!err, "GETDEVCAPS device type: %s\n", dbg_mcierr(err));
142     if(!err) ok( parm.caps.dwReturn == MCI_DEVTYPE_CD_AUDIO, "getdevcaps device type: %u\n", parm.caps.dwReturn);
143 
144     err = mciSendCommandA(wDeviceID, MCI_RECORD, 0, (DWORD_PTR)&parm);
145     ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_RECORD: %s\n", dbg_mcierr(err));
146 
147     /* Wine's MCI_MapMsgAtoW crashes on MCI_SAVE without parm->lpfilename */
148     parm.save.lpfilename = "foo";
149     err = mciSendCommandA(wDeviceID, MCI_SAVE, 0, (DWORD_PTR)&parm);
150     ok(err == MCIERR_UNSUPPORTED_FUNCTION, "MCI_SAVE: %s\n", dbg_mcierr(err));
151 
152     /* commands from the core set are UNSUPPORTED, others UNRECOGNIZED */
153     err = mciSendCommandA(wDeviceID, MCI_STEP, 0, (DWORD_PTR)&parm);
154     ok(err == MCIERR_UNRECOGNIZED_COMMAND, "MCI_STEP: %s\n", dbg_mcierr(err));
155 
156     parm.status.dwItem = MCI_STATUS_TIME_FORMAT;
157     parm.status.dwReturn = 0xFEEDABAD;
158     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
159     ok(!err, "STATUS time format: %s\n", dbg_mcierr(err));
160     if(!err) ok(parm.status.dwReturn == MCI_FORMAT_MSF, "status time default format: %ld\n", parm.status.dwReturn);
161 
162     /* "CD-Audio" */
163     err = mciSendStringA("info c product wait notify", buf, sizeof(buf), hwnd);
164     ok(!err, "info product: %s\n", dbg_mcierr(err));
165     test_notification(hwnd, "info notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
166 
167     parm.status.dwItem = MCI_STATUS_MEDIA_PRESENT;
168     parm.status.dwReturn = 0xFEEDABAD;
169     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
170     ok(err || parm.status.dwReturn == TRUE || parm.status.dwReturn == FALSE,
171        "STATUS media present: %s\n", dbg_mcierr(err));
172 
173     if (parm.status.dwReturn != TRUE) {
174         skip("No CD-ROM in drive.\n");
175         return;
176     }
177 
178     parm.status.dwItem = MCI_STATUS_MODE;
179     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
180     ok(!err, "STATUS mode: %s\n", dbg_mcierr(err));
181     switch(parm.status.dwReturn) {
182     case MCI_MODE_NOT_READY:
183         skip("CD-ROM mode not ready (DVD in drive?)\n");
184         return;
185     case MCI_MODE_OPEN: /* should not happen with MEDIA_PRESENT */
186         skip("CD-ROM drive is open\n");
187         /* set door closed may not work. */
188         return;
189     default: /* play/record/seek/pause */
190         ok(parm.status.dwReturn==MCI_MODE_STOP, "STATUS mode is %lx\n", parm.status.dwReturn);
191         /* fall through */
192     case MCI_MODE_STOP: /* normal */
193         break;
194     }
195 
196     /* Initial mode is "stopped" with a CD in drive */
197     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
198     ok(!err, "status mode: %s\n", dbg_mcierr(err));
199     if(!err) ok(!strcmp(buf, "stopped"), "status mode is initially %s\n", buf);
200 
201     err = mciSendStringA("status c ready", buf, sizeof(buf), hwnd);
202     ok(!err, "status ready: %s\n", dbg_mcierr(err));
203     if(!err) ok(!strcmp(buf, "true"), "status ready with media is %s\n", buf);
204 
205     err = mciSendStringA("info c product identity", buf, sizeof(buf), hwnd);
206     ok(!err, "info 2flags: %s\n", dbg_mcierr(err)); /* not MCIERR_FLAGS_NOT_COMPATIBLE */
207     /* Precedence rule p>u>i verified experimentally, not tested here. */
208 
209     err = mciSendStringA("info c identity", buf, sizeof(buf), hwnd);
210     ok(!err || err == MCIERR_HARDWARE, "info identity: %s\n", dbg_mcierr(err));
211     /* a blank disk causes MCIERR_HARDWARE and other commands to fail likewise. */
212     ok_hw = err;
213 
214     err = mciSendStringA("info c upc", buf, sizeof(buf), hwnd);
215     ok(err == ok_hw || err == MCIERR_NO_IDENTITY, "info upc: %s\n", dbg_mcierr(err));
216 
217     parm.status.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
218     parm.status.dwReturn = 0;
219     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
220     ok(err == ok_hw, "STATUS number of tracks: %s\n", dbg_mcierr(err));
221     numtracks = parm.status.dwReturn;
222     /* cf. MAXIMUM_NUMBER_TRACKS */
223     ok(0 < numtracks && numtracks <= 99, "number of tracks=%ld\n", parm.status.dwReturn);
224 
225     err = mciSendStringA("status c length", buf, sizeof(buf), hwnd);
226     ok(err == ok_hw, "status length: %s\n", dbg_mcierr(err));
227     if(!err) trace("CD length %s\n", buf);
228 
229     if(err) { /* MCIERR_HARDWARE when given a blank disk */
230         skip("status length %s (blank disk?)\n", dbg_mcierr(err));
231         return;
232     }
233 
234     /* Linux leaves the drive at some random position,
235      * native initialises to the start position below. */
236     err = mciSendStringA("status c position", buf, sizeof(buf), hwnd);
237     ok(!err, "status position: %s\n", dbg_mcierr(err));
238     if(!err) todo_wine ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
239                 "status position initially %s\n", buf);
240     /* 2 seconds is the initial position even with data tracks. */
241 
242     err = mciSendStringA("status c position start notify", buf, sizeof(buf), hwnd);
243     ok(err == ok_hw, "status position start: %s\n", dbg_mcierr(err));
244     if(!err) ok(!strcmp(buf, "00:02:00") || !strcmp(buf, "00:02:33") || !strcmp(buf, "00:03:00"),
245                 "status position start %s\n", buf);
246     test_notification(hwnd, "status notify", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
247 
248     err = mciSendStringA("status c position start track 1 notify", buf, sizeof(buf), hwnd);
249     ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start: %s\n", dbg_mcierr(err));
250     test_notification(hwnd, "status 2flags", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
251 
252     err = mciSendStringA("play c from 00:02:00 to 00:01:00 notify", buf, sizeof(buf), hwnd);
253     ok(err == MCIERR_OUTOFRANGE, "play 2s to 1s: %s\n", dbg_mcierr(err));
254     test_notification(hwnd, "play 2s to 1s", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
255 
256     err = mciSendStringA("resume c", buf, sizeof(buf), hwnd);
257     ok(err == MCIERR_HARDWARE || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
258        "resume without play: %s\n", dbg_mcierr(err)); /* not NONAPPLICABLE_FUNCTION */
259     /* vmware with a .iso (data-only) yields no error on NT/w2k */
260 
261     err = mciSendStringA("seek c wait", buf, sizeof(buf), hwnd);
262     ok(err == MCIERR_MISSING_PARAMETER, "seek noflag: %s\n", dbg_mcierr(err));
263 
264     err = mciSendStringA("seek c to start to end", buf, sizeof(buf), hwnd);
265     ok(err == MCIERR_FLAGS_NOT_COMPATIBLE || broken(!err), "seek to start+end: %s\n", dbg_mcierr(err));
266     /* Win9x only errors out with Seek to start to <position> */
267 
268     /* set Wine to a defined position before play */
269     err = mciSendStringA("seek c to start notify", buf, sizeof(buf), hwnd);
270     ok(!err, "seek to start: %s\n", dbg_mcierr(err));
271     test_notification(hwnd, "seek to start", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
272     /* Win9X Status position / current track then sometimes report the end position / track! */
273 
274     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
275     ok(!err, "status mode: %s\n", dbg_mcierr(err));
276     if(!err) ok(!strcmp(buf, "stopped"), "status mode after seek is %s\n", buf);
277 
278     /* MCICDA ignores MCI_SET_VIDEO */
279     err = mciSendStringA("set c video on", buf, sizeof(buf), hwnd);
280     ok(!err, "set video: %s\n", dbg_mcierr(err));
281 
282     /* One xp machine ignored SET_AUDIO, one w2k and one w7 machine honoured it
283      * and simultaneously toggled the mute button in the mixer control panel.
284      * Or does it only depend on the HW, not the OS?
285      * Some vmware machines return MCIERR_HARDWARE. */
286     err = mciSendStringA("set c audio all on", buf, sizeof(buf), hwnd);
287     ok(!err || err == MCIERR_HARDWARE, "set audio: %s\n", dbg_mcierr(err));
288 
289     err = mciSendStringA("set c time format ms", buf, sizeof(buf), hwnd);
290     ok(!err, "set time format ms: %s\n", dbg_mcierr(err));
291 
292     memset(buf, 0, sizeof(buf));
293     err = mciSendStringA("status c position start", buf, sizeof(buf), hwnd);
294     ok(!err, "status position start (ms): %s\n", dbg_mcierr(err));
295     duration = atoi(buf);
296     if(!err) ok(duration > 2000, "status position initially %sms\n", buf);
297     /* 00:02:00 corresponds to 2001 ms, 02:33 -> 2441 etc. */
298 
299     err = mciSendStringA("status c position start track 1", buf, sizeof(buf), hwnd);
300     ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "status position start+track: %s\n", dbg_mcierr(err));
301 
302     err = mciSendStringA("status c notify wait", buf, sizeof(buf), hwnd);
303     ok(err == MCIERR_MISSING_PARAMETER, "status noflag: %s\n", dbg_mcierr(err));
304 
305     err = mciSendStringA("status c length track 1", buf, sizeof(buf), hwnd);
306     ok(!err, "status length (ms): %s\n", dbg_mcierr(err));
307     if(!err) {
308         trace("track #1 length %sms\n", buf);
309         duration = atoi(buf);
310     } else duration = 2001; /* for the position test below */
311 
312     if (0) { /* causes some native systems to return Seek and Play with MCIERR_HARDWARE */
313         /* depending on capability can eject only? */
314         err = mciSendStringA("set c door closed notify", buf, sizeof(buf), hwnd);
315         ok(!err, "set door closed: %s\n", dbg_mcierr(err));
316         test_notification(hwnd, "door closed", err ? 0 : MCI_NOTIFY_SUCCESSFUL);
317     }
318     /* Changing the disk while the MCI device is open causes the Status
319      * command to report stale data.  Native obviously caches the TOC. */
320 
321     /* status type track is localised, strcmp("audio|other") may fail. */
322     parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
323     parm.status.dwTrack = 1;
324     parm.status.dwReturn = 0xFEEDABAD;
325     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
326     ok(!err, "STATUS type track 1: %s\n", dbg_mcierr(err));
327     ok(parm.status.dwReturn==MCI_CDA_TRACK_OTHER || parm.status.dwReturn==MCI_CDA_TRACK_AUDIO,
328        "unknown track type %lx\n", parm.status.dwReturn);
329 
330     if (parm.status.dwReturn == MCI_CDA_TRACK_OTHER) {
331         /* Find an audio track */
332         parm.status.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
333         parm.status.dwTrack = numtracks;
334         parm.status.dwReturn = 0xFEEDABAD;
335         err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
336         ok(!err, "STATUS type track %u: %s\n", numtracks, dbg_mcierr(err));
337         ok(parm.status.dwReturn == MCI_CDA_TRACK_OTHER || parm.status.dwReturn == MCI_CDA_TRACK_AUDIO,
338            "unknown track type %lx\n", parm.status.dwReturn);
339         track = (!err && parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) ? numtracks : 0;
340 
341         /* Seek to start (above) skips over data tracks
342          * In case of a data only CD, it seeks to the end of disk, however
343          * another Status position a few seconds later yields MCIERR_HARDWARE. */
344         parm.status.dwItem = MCI_STATUS_POSITION;
345         parm.status.dwReturn = 2000;
346         err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&parm);
347         ok(!err || broken(err == MCIERR_HARDWARE), "STATUS position: %s\n", dbg_mcierr(err));
348 
349         if(!err && track) ok(parm.status.dwReturn > duration,
350             "Seek did not skip data tracks, position %lums\n", parm.status.dwReturn);
351         /* dwReturn > start + length(#1) may fail because of small position report fluctuation.
352          * On some native systems, status position fluctuates around the target position;
353          * Successive calls return varying positions! */
354 
355         err = mciSendStringA("set c time format msf", buf, sizeof(buf), hwnd);
356         ok(!err, "set time format msf: %s\n", dbg_mcierr(err));
357 
358         parm.status.dwItem = MCI_STATUS_LENGTH;
359         parm.status.dwTrack = 1;
360         parm.status.dwReturn = 0xFEEDABAD;
361         err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
362         ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
363         duration = parm.status.dwReturn;
364         trace("track #1 length: %02um:%02us:%02uframes\n",
365               MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
366         ok(duration>>24==0, "CD length high bits %08X\n", duration);
367 
368         /* TODO only with mixed CDs? */
369         /* play track 1 to length silently works with data tracks */
370         parm.play.dwFrom = MCI_MAKE_MSF(0,2,0);
371         parm.play.dwTo = duration; /* omitting 2 seconds from end */
372         err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR)&parm);
373         ok(!err, "PLAY data to %08X: %s\n", duration, dbg_mcierr(err));
374 
375         Sleep(1500*factor); /* Time to spin up, hopefully less than track length */
376 
377         err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
378         ok(!err, "status mode: %s\n", dbg_mcierr(err));
379         if(!err) ok(!strcmp(buf, "stopped"), "status mode on data is %s\n", buf);
380     } else if (parm.status.dwReturn == MCI_CDA_TRACK_AUDIO) {
381         skip("Got no mixed data+audio CD.\n");
382         track = 1;
383     } else track = 0;
384 
385     if (!track) {
386         skip("Found no audio track.\n");
387         return;
388     }
389 
390     err = mciSendStringA("set c time format msf", buf, sizeof(buf), hwnd);
391     ok(!err, "set time format msf: %s\n", dbg_mcierr(err));
392 
393     parm.status.dwItem = MCI_STATUS_LENGTH;
394     parm.status.dwTrack = numtracks;
395     parm.status.dwReturn = 0xFEEDABAD;
396     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
397     ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
398     duration = parm.status.dwReturn;
399     trace("last track length: %02um:%02us:%02uframes\n",
400           MCI_MSF_MINUTE(duration), MCI_MSF_SECOND(duration), MCI_MSF_FRAME(duration));
401     ok(duration>>24==0, "CD length high bits %08X\n", duration);
402 
403     parm.status.dwItem = MCI_STATUS_POSITION;
404     /* dwTrack is still set */
405     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
406     ok(!err, "STATUS position start track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
407     trace("last track position: %02um:%02us:%02uframes\n",
408           MCI_MSF_MINUTE(parm.status.dwReturn), MCI_MSF_SECOND(parm.status.dwReturn), MCI_MSF_FRAME(parm.status.dwReturn));
409 
410     /* Seek to position + length always works, esp.
411      * for the last track it's NOT the position of the lead-out. */
412     parm.seek.dwTo = MSF_Add(parm.status.dwReturn, duration);
413     err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
414     ok(!err, "SEEK to %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err));
415 
416     parm.seek.dwTo = MSF_Add(parm.seek.dwTo, MCI_MAKE_MSF(0,0,1));
417     err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
418     ok(err == MCIERR_OUTOFRANGE, "SEEK past %08X position last + length: %s\n", parm.seek.dwTo, dbg_mcierr(err));
419 
420     err = mciSendStringA("set c time format tmsf", buf, sizeof(buf), hwnd);
421     ok(!err, "set time format tmsf: %s\n", dbg_mcierr(err));
422 
423     parm.play.dwFrom = track;
424     err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD_PTR)&parm);
425     ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
426 
427     if(err) {
428         skip("Cannot manage to play track %u.\n", track);
429         return;
430     }
431 
432     Sleep(1800*factor); /* Time to spin up, hopefully less than track length */
433 
434     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
435     ok(!err, "status mode: %s\n", dbg_mcierr(err));
436     if(!err) ok(!strcmp(buf, "playing"), "status mode during play is %s\n", buf);
437 
438     err = mciSendStringA("pause c", buf, sizeof(buf), hwnd);
439     ok(!err, "pause: %s\n", dbg_mcierr(err));
440 
441     test_notification(hwnd, "pause should abort notification", MCI_NOTIFY_ABORTED);
442 
443     /* Native returns stopped when paused,
444      * yet the Stop command is different as it would disallow Resume. */
445     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
446     ok(!err, "status mode: %s\n", dbg_mcierr(err));
447     if(!err) todo_wine ok(!strcmp(buf, "stopped"), "status mode while paused is %s\n", buf);
448 
449     err = mciSendCommandA(wDeviceID, MCI_RESUME, 0, 0);
450     ok(!err || /* Win9x */ err == MCIERR_UNSUPPORTED_FUNCTION,
451        "RESUME without parms: %s\n", dbg_mcierr(err));
452 
453     Sleep(1300*factor);
454 
455     /* Native continues to play without interruption */
456     err = mciSendCommandA(wDeviceID, MCI_PLAY, 0, 0);
457     todo_wine ok(!err, "PLAY without parms: %s\n", dbg_mcierr(err));
458 
459     parm.play.dwFrom = MCI_MAKE_TMSF(numtracks,0,1,0);
460     parm.play.dwTo = 1;
461     err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO, (DWORD_PTR)&parm);
462     ok(err == MCIERR_OUTOFRANGE, "PLAY: %s\n", dbg_mcierr(err));
463 
464     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
465     ok(!err, "status mode: %s\n", dbg_mcierr(err));
466     if(!err) ok(!strcmp(buf, "playing"), "status mode after play is %s\n", buf);
467 
468     err = mciSendCommandA(wDeviceID, MCI_STOP, MCI_NOTIFY, (DWORD_PTR)&parm);
469     ok(!err, "STOP notify: %s\n", dbg_mcierr(err));
470     test_notification(hwnd, "STOP notify", MCI_NOTIFY_SUCCESSFUL);
471     test_notification(hwnd, "STOP #1", 0);
472 
473     parm.play.dwFrom = track;
474     err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD_PTR)&parm);
475     ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
476 
477     Sleep(1600*factor);
478 
479     parm.seek.dwTo = 1; /* not <track>, to test position below */
480     err = mciSendCommandA(wDeviceID, MCI_SEEK, MCI_TO, (DWORD_PTR)&parm);
481     ok(!err, "SEEK to %u notify: %s\n", track, dbg_mcierr(err));
482     /* Note that native's Status position / current track may move the head
483      * and reflect the new position only seconds after issuing the command. */
484 
485     /* Seek stops */
486     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
487     ok(!err, "status mode: %s\n", dbg_mcierr(err));
488     if(!err) ok(!strcmp(buf, "stopped"), "status mode after play is %s\n", buf);
489 
490     test_notification(hwnd, "Seek aborts Play", MCI_NOTIFY_ABORTED);
491     test_notification(hwnd, "Seek", 0);
492 
493     parm.play.dwFrom = track;
494     parm.play.dwTo = MCI_MAKE_TMSF(track,0,0,21); /* 21 frames, subsecond */
495     err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR)&parm);
496     ok(!err, "PLAY from %u notify: %s\n", track, dbg_mcierr(err));
497 
498     Sleep(2200*factor);
499 
500     err = mciSendStringA("status c mode", buf, sizeof(buf), hwnd);
501     ok(!err, "status mode: %s\n", dbg_mcierr(err));
502     if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf);
503     if(!err && !strcmp(buf, "playing")) trace("status playing after sleep\n");
504 
505     /* Playing to end asynchronously sends no notification! */
506     test_notification(hwnd, "PLAY to end", 0);
507 
508     err = mciSendStringA("status c mode notify", buf, sizeof(buf), hwnd);
509     ok(!err, "status mode: %s\n", dbg_mcierr(err));
510     if(!err) ok(!strcmp(buf, "stopped") || broken(!strcmp(buf, "playing")), "status mode after play is %s\n", buf);
511     if(!err && !strcmp(buf, "playing")) trace("status still playing\n");
512     /* Some systems report playing even after Sleep(3900ms) yet the successful
513      * notification tests (not ABORTED) indicates they are finished. */
514 
515     test_notification(hwnd, "dangling from PLAY", MCI_NOTIFY_SUPERSEDED);
516     test_notification(hwnd, "status mode", MCI_NOTIFY_SUCCESSFUL);
517 
518     err = mciSendStringA("stop c", buf, sizeof(buf), hwnd);
519     ok(!err, "stop: %s\n", dbg_mcierr(err));
520 
521     test_notification(hwnd, "PLAY to end", 0);
522 
523     /* length as MSF despite set time format TMSF */
524     parm.status.dwItem = MCI_STATUS_LENGTH;
525     parm.status.dwTrack = numtracks;
526     parm.status.dwReturn = 0xFEEDABAD;
527     err = mciSendCommandA(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD_PTR)&parm);
528     ok(!err, "STATUS length track %u: %s\n", parm.status.dwTrack, dbg_mcierr(err));
529     ok(duration == parm.status.dwReturn, "length MSF<>TMSF %08lX\n", parm.status.dwReturn);
530 
531     /* Play from position start to start+length always works. */
532     /* TODO? also play it using MSF */
533     parm.play.dwFrom = numtracks;
534     parm.play.dwTo = (duration << 8) | numtracks; /* as TMSF */
535     err = mciSendCommandA(wDeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY, (DWORD_PTR)&parm);
536     ok(!err, "PLAY (TMSF) from %08X to %08X: %s\n", parm.play.dwFrom, parm.play.dwTo, dbg_mcierr(err));
537 
538     Sleep(1400*factor);
539 
540     err = mciSendStringA("status c current track", buf, sizeof(buf), hwnd);
541     ok(!err, "status track: %s\n", dbg_mcierr(err));
542     if(!err) todo_wine ok(numtracks == atoi(buf), "status current track gave %s, expected %u\n", buf, numtracks);
543     /* fails in Wine because SEEK is independent on IOCTL_CDROM_RAW_READ */
544 
545     err = mciSendCommandA(wDeviceID, MCI_STOP, 0, 0);
546     ok(!err, "STOP: %s\n", dbg_mcierr(err));
547     test_notification(hwnd, "STOP aborts", MCI_NOTIFY_ABORTED);
548     test_notification(hwnd, "STOP final", 0);
549 }
550 
551 static void test_openclose(HWND hwnd)
552 {
553     MCIDEVICEID wDeviceID;
554     MCI_PARMS_UNION parm;
555     MCIERROR err;
556     char drive[] = {'a',':','\\','X','\0'};
557     if (ok_open == MCIERR_CANNOT_LOAD_DRIVER) {
558         /* todo_wine Every open below should yield this same error. */
559         skip("CD-ROM device likely not installed or disabled.\n");
560         return;
561     }
562 
563     /* Bug in native since NT: After OPEN "c" without MCI_OPEN_ALIAS fails with
564      * MCIERR_DEVICE_OPEN, any subsequent OPEN fails with EXTENSION_NOT_FOUND! */
565     parm.open.lpstrAlias = "x"; /* with alias, OPEN "c" behaves normally */
566     parm.open.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO;
567     parm.open.lpstrElementName = drive;
568     for ( ; strlen(drive); drive[strlen(drive)-1] = 0)
569     for (drive[0] = 'a'; drive[0] <= 'z'; drive[0]++) {
570         err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |
571                 MCI_OPEN_SHAREABLE | MCI_OPEN_ALIAS, (DWORD_PTR)&parm);
572         ok(!err || err == MCIERR_INVALID_FILE, "OPEN %s type: %s\n", drive, dbg_mcierr(err));
573         /* open X:\ fails in Win9x/NT. Only open X: works everywhere. */
574         if(!err) {
575             wDeviceID = parm.open.wDeviceID;
576             trace("ok with %s\n", drive);
577             err = mciSendCommandA(wDeviceID, MCI_CLOSE, 0, 0);
578             ok(!err,"mciCommand close returned %s\n", dbg_mcierr(err));
579         }
580     }
581     drive[0] = '\\';
582     err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID |
583             MCI_OPEN_SHAREABLE, (DWORD_PTR)&parm);
584     ok(err == MCIERR_INVALID_FILE, "OPEN %s type: %s\n", drive, dbg_mcierr(err));
585     if(!err) mciSendCommandA(parm.open.wDeviceID, MCI_CLOSE, 0, 0);
586 
587     if (0) {
588         parm.open.lpstrElementName = (LPCSTR)0xDEADBEEF;
589         err = mciSendCommandA(0, MCI_OPEN, MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID |
590                 MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE, (DWORD_PTR)&parm);
591         todo_wine ok(err == MCIERR_FLAGS_NOT_COMPATIBLE, "OPEN elt_ID: %s\n", dbg_mcierr(err));
592         if(!err) mciSendCommandA(parm.open.wDeviceID, MCI_CLOSE, 0, 0);
593     }
594 }
595 
596 START_TEST(mcicda)
597 {
598     MCIERROR err;
599     HWND hwnd;
600     hwnd = CreateWindowExA(0, "static", "mcicda test", WS_POPUP, 0,0,100,100,
601                            0, 0, 0, NULL);
602     test_notification(hwnd, "-prior to tests-", 0);
603     test_play(hwnd);
604     test_openclose(hwnd);
605     err = mciSendCommandA(MCI_ALL_DEVICE_ID, MCI_STOP, 0, 0);
606     todo_wine ok(!err || broken(err == MCIERR_HARDWARE /* blank CD or testbot without CD-ROM */),
607        "STOP all returned %s\n", dbg_mcierr(err));
608     err = mciSendStringA("close all", NULL, 0, hwnd);
609     ok(!err, "final close all returned %s\n", dbg_mcierr(err));
610     test_notification(hwnd, "-tests complete-", 0);
611     DestroyWindow(hwnd);
612 }
613