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
;
33 MCI_GENERIC_PARMS gen
;
36 extern const char* dbg_mcierr(MCIERROR err
); /* from mci.c */
38 static BOOL
spurious_message(LPMSG msg
)
40 /* WM_DEVICECHANGE 0x0219 appears randomly */
41 if(msg
->message
!= MM_MCINOTIFY
) {
42 trace("skipping spurious message %04x\n",msg
->message
);
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 */
54 do { seen
= PeekMessageA(&msg
, hwnd
, 0, 0, PM_REMOVE
); }
55 while(seen
&& spurious_message(&msg
));
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
);
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
);
72 #define CDFRAMES_PERSEC 75
73 static DWORD
MSF_Add(DWORD d1
, DWORD d2
)
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
;
82 m
= MCI_MSF_MINUTE(d1
) + MCI_MSF_MINUTE(d2
) + c
; /* may be > 60 */
83 return MCI_MAKE_MSF(m
,s
,f
);
86 static MCIERROR ok_open
= 0; /* MCIERR_CANNOT_LOAD_DRIVER */
88 /* TODO show that shareable flag is not what Wine implements. */
90 static void test_play(HWND hwnd
)
92 MCIDEVICEID wDeviceID
;
95 DWORD numtracks
, track
, duration
;
96 DWORD factor
= winetest_interactive
? 3 : 1;
98 memset(buf
, 0, sizeof(buf
));
99 parm
.gen
.dwCallback
= (DWORD_PTR
)hwnd
; /* once to rule them all */
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
));
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. */
111 skip("Cannot open any cdaudio device, %s.\n", dbg_mcierr(err
));
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. */
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
);
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
);
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
);
131 err
= mciSendStringA("capability c", buf
, sizeof(buf
), NULL
);
132 ok(err
== MCIERR_MISSING_PARAMETER
, "capability nokeyword: %s\n", dbg_mcierr(err
));
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
));
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
);
144 err
= mciSendCommandA(wDeviceID
, MCI_RECORD
, 0, (DWORD_PTR
)&parm
);
145 ok(err
== MCIERR_UNSUPPORTED_FUNCTION
, "MCI_RECORD: %s\n", dbg_mcierr(err
));
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
));
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
));
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
);
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
);
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
));
173 if (parm
.status
.dwReturn
!= TRUE
) {
174 skip("No CD-ROM in drive.\n");
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");
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. */
189 default: /* play/record/seek/pause */
190 ok(parm
.status
.dwReturn
==MCI_MODE_STOP
, "STATUS mode is %lx\n", parm
.status
.dwReturn
);
192 case MCI_MODE_STOP
: /* normal */
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
);
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
);
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. */
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. */
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
));
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
);
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
);
229 if(err
) { /* MCIERR_HARDWARE when given a blank disk */
230 skip("status length %s (blank disk?)\n", dbg_mcierr(err
));
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. */
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
);
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
);
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
);
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 */
261 err
= mciSendStringA("seek c wait", buf
, sizeof(buf
), hwnd
);
262 ok(err
== MCIERR_MISSING_PARAMETER
, "seek noflag: %s\n", dbg_mcierr(err
));
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> */
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! */
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
);
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
));
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
));
289 err
= mciSendStringA("set c time format ms", buf
, sizeof(buf
), hwnd
);
290 ok(!err
, "set time format ms: %s\n", dbg_mcierr(err
));
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. */
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
));
302 err
= mciSendStringA("status c notify wait", buf
, sizeof(buf
), hwnd
);
303 ok(err
== MCIERR_MISSING_PARAMETER
, "status noflag: %s\n", dbg_mcierr(err
));
305 err
= mciSendStringA("status c length track 1", buf
, sizeof(buf
), hwnd
);
306 ok(!err
, "status length (ms): %s\n", dbg_mcierr(err
));
308 trace("track #1 length %sms\n", buf
);
309 duration
= atoi(buf
);
310 } else duration
= 2001; /* for the position test below */
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
);
318 /* Changing the disk while the MCI device is open causes the Status
319 * command to report stale data. Native obviously caches the TOC. */
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
);
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;
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
));
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! */
355 err
= mciSendStringA("set c time format msf", buf
, sizeof(buf
), hwnd
);
356 ok(!err
, "set time format msf: %s\n", dbg_mcierr(err
));
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
);
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
));
375 Sleep(1500*factor
); /* Time to spin up, hopefully less than track length */
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");
386 skip("Found no audio track.\n");
390 err
= mciSendStringA("set c time format msf", buf
, sizeof(buf
), hwnd
);
391 ok(!err
, "set time format msf: %s\n", dbg_mcierr(err
));
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
);
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
));
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
));
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
));
420 err
= mciSendStringA("set c time format tmsf", buf
, sizeof(buf
), hwnd
);
421 ok(!err
, "set time format tmsf: %s\n", dbg_mcierr(err
));
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
));
428 skip("Cannot manage to play track %u.\n", track
);
432 Sleep(1800*factor
); /* Time to spin up, hopefully less than track length */
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
);
438 err
= mciSendStringA("pause c", buf
, sizeof(buf
), hwnd
);
439 ok(!err
, "pause: %s\n", dbg_mcierr(err
));
441 test_notification(hwnd
, "pause should abort notification", MCI_NOTIFY_ABORTED
);
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
);
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
));
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
));
459 parm
.play
.dwFrom
= MCI_MAKE_TMSF(numtracks
,0,1,0);
461 err
= mciSendCommandA(wDeviceID
, MCI_PLAY
, MCI_FROM
| MCI_TO
, (DWORD_PTR
)&parm
);
462 ok(err
== MCIERR_OUTOFRANGE
, "PLAY: %s\n", dbg_mcierr(err
));
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
);
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);
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
));
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. */
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
);
490 test_notification(hwnd
, "Seek aborts Play", MCI_NOTIFY_ABORTED
);
491 test_notification(hwnd
, "Seek", 0);
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
));
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");
505 /* Playing to end asynchronously sends no notification! */
506 test_notification(hwnd
, "PLAY to end", 0);
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. */
515 test_notification(hwnd
, "dangling from PLAY", MCI_NOTIFY_SUPERSEDED
);
516 test_notification(hwnd
, "status mode", MCI_NOTIFY_SUCCESSFUL
);
518 err
= mciSendStringA("stop c", buf
, sizeof(buf
), hwnd
);
519 ok(!err
, "stop: %s\n", dbg_mcierr(err
));
521 test_notification(hwnd
, "PLAY to end", 0);
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
);
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
));
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 */
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);
551 static void test_openclose(HWND hwnd
)
553 MCIDEVICEID wDeviceID
;
554 MCI_PARMS_UNION parm
;
556 char drive
[] = "a:\\X";
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");
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. */
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
));
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);
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);
600 hwnd
= CreateWindowExA(0, "static", "mcicda test", WS_POPUP
, 0,0,100,100,
602 test_notification(hwnd
, "-prior to tests-", 0);
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);