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