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