1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#ifdef SDL_CDROM_MACOSX
25
26#include "SDL_syscdrom_c.h"
27
28#pragma mark -- Globals --
29
30static FSRef**         tracks;
31static FSVolumeRefNum* volumes;
32static CDstatus        status;
33static int             nextTrackFrame;
34static int             nextTrackFramesRemaining;
35static int             fakeCD;
36static int             currentTrack;
37static int             didReadTOC;
38static int             cacheTOCNumTracks;
39static int             currentDrive; /* Only allow 1 drive in use at a time */
40
41#pragma mark -- Prototypes --
42
43static const char *SDL_SYS_CDName   (int drive);
44static int         SDL_SYS_CDOpen   (int drive);
45static int         SDL_SYS_CDGetTOC (SDL_CD *cdrom);
46static CDstatus    SDL_SYS_CDStatus (SDL_CD *cdrom, int *position);
47static int         SDL_SYS_CDPlay   (SDL_CD *cdrom, int start, int length);
48static int         SDL_SYS_CDPause  (SDL_CD *cdrom);
49static int         SDL_SYS_CDResume (SDL_CD *cdrom);
50static int         SDL_SYS_CDStop   (SDL_CD *cdrom);
51static int         SDL_SYS_CDEject  (SDL_CD *cdrom);
52static void        SDL_SYS_CDClose  (SDL_CD *cdrom);
53
54#pragma mark -- Helper Functions --
55
56/* Read a list of tracks from the volume */
57static int LoadTracks (SDL_CD *cdrom)
58{
59    /* Check if tracks are already loaded */
60    if  ( tracks[cdrom->id] != NULL )
61        return 0;
62
63    /* Allocate memory for tracks */
64    tracks[cdrom->id] = (FSRef*) SDL_calloc (1, sizeof(**tracks) * cdrom->numtracks);
65    if (tracks[cdrom->id] == NULL) {
66        SDL_OutOfMemory ();
67        return -1;
68    }
69
70    /* Load tracks */
71    if (ListTrackFiles (volumes[cdrom->id], tracks[cdrom->id], cdrom->numtracks) < 0)
72        return -1;
73
74    return 0;
75}
76
77/* Find a file for a given start frame and length */
78static FSRef* GetFileForOffset (SDL_CD *cdrom, int start, int length,  int *outStartFrame, int *outStopFrame)
79{
80    int i;
81
82    for (i = 0; i < cdrom->numtracks; i++) {
83
84        if (cdrom->track[i].offset <= start &&
85            start < (cdrom->track[i].offset + cdrom->track[i].length))
86            break;
87    }
88
89    if (i == cdrom->numtracks)
90        return NULL;
91
92    currentTrack = i;
93
94    *outStartFrame = start - cdrom->track[i].offset;
95
96    if ((*outStartFrame + length) < cdrom->track[i].length) {
97        *outStopFrame = *outStartFrame + length;
98        length = 0;
99        nextTrackFrame = -1;
100        nextTrackFramesRemaining = -1;
101    }
102    else {
103        *outStopFrame = -1;
104        length -= cdrom->track[i].length - *outStartFrame;
105        nextTrackFrame = cdrom->track[i+1].offset;
106        nextTrackFramesRemaining = length;
107    }
108
109    return &tracks[cdrom->id][i];
110}
111
112/* Setup another file for playback, or stop playback (called from another thread) */
113static void CompletionProc (SDL_CD *cdrom)
114{
115
116    Lock ();
117
118    if (nextTrackFrame > 0 && nextTrackFramesRemaining > 0) {
119
120        /* Load the next file to play */
121        int startFrame, stopFrame;
122        FSRef *file;
123
124        PauseFile ();
125        ReleaseFile ();
126
127        file = GetFileForOffset (cdrom, nextTrackFrame,
128            nextTrackFramesRemaining, &startFrame, &stopFrame);
129
130        if (file == NULL) {
131            status = CD_STOPPED;
132            Unlock ();
133            return;
134        }
135
136        LoadFile (file, startFrame, stopFrame);
137
138        SetCompletionProc (CompletionProc, cdrom);
139
140        PlayFile ();
141    }
142    else {
143
144        /* Release the current file */
145        PauseFile ();
146        ReleaseFile ();
147        status = CD_STOPPED;
148    }
149
150    Unlock ();
151}
152
153
154#pragma mark -- Driver Functions --
155
156/* Initialize */
157int SDL_SYS_CDInit (void)
158{
159    /* Initialize globals */
160    volumes = NULL;
161    tracks  = NULL;
162    status  = CD_STOPPED;
163    nextTrackFrame = -1;
164    nextTrackFramesRemaining = -1;
165    fakeCD  = SDL_FALSE;
166    currentTrack = -1;
167    didReadTOC = SDL_FALSE;
168    cacheTOCNumTracks = -1;
169    currentDrive = -1;
170
171    /* Fill in function pointers */
172    SDL_CDcaps.Name   = SDL_SYS_CDName;
173    SDL_CDcaps.Open   = SDL_SYS_CDOpen;
174    SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC;
175    SDL_CDcaps.Status = SDL_SYS_CDStatus;
176    SDL_CDcaps.Play   = SDL_SYS_CDPlay;
177    SDL_CDcaps.Pause  = SDL_SYS_CDPause;
178    SDL_CDcaps.Resume = SDL_SYS_CDResume;
179    SDL_CDcaps.Stop   = SDL_SYS_CDStop;
180    SDL_CDcaps.Eject  = SDL_SYS_CDEject;
181    SDL_CDcaps.Close  = SDL_SYS_CDClose;
182
183    /*
184        Read the list of "drives"
185
186        This is currently a hack that infers drives from
187        mounted audio CD volumes, rather than
188        actual CD-ROM devices - which means it may not
189        act as expected sometimes.
190    */
191
192    /* Find out how many cd volumes are mounted */
193    SDL_numcds = DetectAudioCDVolumes (NULL, 0);
194
195    /*
196        If there are no volumes, fake a cd device
197        so tray empty can be reported.
198    */
199    if (SDL_numcds == 0) {
200
201        fakeCD = SDL_TRUE;
202        SDL_numcds = 1;
203        status = CD_TRAYEMPTY;
204
205        return 0;
206    }
207
208    /* Allocate space for volumes */
209    volumes = (FSVolumeRefNum*) SDL_calloc (1, sizeof(*volumes) * SDL_numcds);
210    if (volumes == NULL) {
211        SDL_OutOfMemory ();
212        return -1;
213    }
214
215    /* Allocate space for tracks */
216    tracks = (FSRef**) SDL_calloc (1, sizeof(*tracks) * (SDL_numcds + 1));
217    if (tracks == NULL) {
218        SDL_OutOfMemory ();
219        return -1;
220    }
221
222    /* Mark the end of the tracks array */
223    tracks[ SDL_numcds ] = (FSRef*)-1;
224
225    /*
226        Redetect, now save all volumes for later
227        Update SDL_numcds just in case it changed
228    */
229    {
230        int numVolumes = SDL_numcds;
231
232        SDL_numcds = DetectAudioCDVolumes (volumes, numVolumes);
233
234        /* If more cds suddenly show up, ignore them */
235        if (SDL_numcds > numVolumes) {
236            SDL_SetError ("Some CD's were added but they will be ignored");
237            SDL_numcds = numVolumes;
238        }
239    }
240
241    return 0;
242}
243
244/* Shutdown and cleanup */
245void SDL_SYS_CDQuit(void)
246{
247    ReleaseFile();
248
249    if (volumes != NULL)
250        free (volumes);
251
252    if (tracks != NULL) {
253
254        FSRef **ptr;
255        for (ptr = tracks; *ptr != (FSRef*)-1; ptr++)
256            if (*ptr != NULL)
257                free (*ptr);
258
259        free (tracks);
260    }
261}
262
263/* Get the Unix disk name of the volume */
264static const char *SDL_SYS_CDName (int drive)
265{
266    /*
267     * !!! FIXME: PBHGetVolParmsSync() is gone in 10.6,
268     * !!! FIXME:  replaced with FSGetVolumeParms(), which
269     * !!! FIXME:  isn't available before 10.5.  :/
270     */
271    return "Mac OS X CD-ROM Device";
272
273#if 0
274    OSStatus     err = noErr;
275    HParamBlockRec  pb;
276    GetVolParmsInfoBuffer   volParmsInfo;
277
278    if (fakeCD)
279        return "Fake CD-ROM Device";
280
281    pb.ioParam.ioNamePtr = NULL;
282    pb.ioParam.ioVRefNum = volumes[drive];
283    pb.ioParam.ioBuffer = (Ptr)&volParmsInfo;
284    pb.ioParam.ioReqCount = (SInt32)sizeof(volParmsInfo);
285    err = PBHGetVolParmsSync(&pb);
286
287    if (err != noErr) {
288        SDL_SetError ("PBHGetVolParmsSync returned %d", err);
289        return NULL;
290    }
291
292    return volParmsInfo.vMDeviceID;
293#endif
294}
295
296/* Open the "device" */
297static int SDL_SYS_CDOpen (int drive)
298{
299    /* Only allow 1 device to be open */
300    if (currentDrive >= 0) {
301        SDL_SetError ("Only one cdrom is supported");
302        return -1;
303    }
304    else
305        currentDrive = drive;
306
307    return drive;
308}
309
310/* Get the table of contents */
311static int SDL_SYS_CDGetTOC (SDL_CD *cdrom)
312{
313    if (fakeCD) {
314        SDL_SetError (kErrorFakeDevice);
315        return -1;
316    }
317
318    if (didReadTOC) {
319        cdrom->numtracks = cacheTOCNumTracks;
320        return 0;
321    }
322
323
324    ReadTOCData (volumes[cdrom->id], cdrom);
325    didReadTOC = SDL_TRUE;
326    cacheTOCNumTracks = cdrom->numtracks;
327
328    return 0;
329}
330
331/* Get CD-ROM status */
332static CDstatus SDL_SYS_CDStatus (SDL_CD *cdrom, int *position)
333{
334    if (position) {
335        int trackFrame;
336
337        Lock ();
338        trackFrame = GetCurrentFrame ();
339        Unlock ();
340
341        *position = cdrom->track[currentTrack].offset + trackFrame;
342    }
343
344    return status;
345}
346
347/* Start playback */
348static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length)
349{
350    int startFrame, stopFrame;
351    FSRef *ref;
352
353    if (fakeCD) {
354        SDL_SetError (kErrorFakeDevice);
355        return -1;
356    }
357
358    Lock();
359
360    if (LoadTracks (cdrom) < 0)
361        return -2;
362
363    if (PauseFile () < 0)
364        return -3;
365
366    if (ReleaseFile () < 0)
367        return -4;
368
369    ref = GetFileForOffset (cdrom, start, length, &startFrame, &stopFrame);
370    if (ref == NULL) {
371        SDL_SetError ("SDL_SYS_CDPlay: No file for start=%d, length=%d", start, length);
372        return -5;
373    }
374
375    if (LoadFile (ref, startFrame, stopFrame) < 0)
376        return -6;
377
378    SetCompletionProc (CompletionProc, cdrom);
379
380    if (PlayFile () < 0)
381        return -7;
382
383    status = CD_PLAYING;
384
385    Unlock();
386
387    return 0;
388}
389
390/* Pause playback */
391static int SDL_SYS_CDPause(SDL_CD *cdrom)
392{
393    if (fakeCD) {
394        SDL_SetError (kErrorFakeDevice);
395        return -1;
396    }
397
398    Lock ();
399
400    if (PauseFile () < 0) {
401        Unlock ();
402        return -2;
403    }
404
405    status = CD_PAUSED;
406
407    Unlock ();
408
409    return 0;
410}
411
412/* Resume playback */
413static int SDL_SYS_CDResume(SDL_CD *cdrom)
414{
415    if (fakeCD) {
416        SDL_SetError (kErrorFakeDevice);
417        return -1;
418    }
419
420    Lock ();
421
422    if (PlayFile () < 0) {
423        Unlock ();
424        return -2;
425    }
426
427    status = CD_PLAYING;
428
429    Unlock ();
430
431    return 0;
432}
433
434/* Stop playback */
435static int SDL_SYS_CDStop(SDL_CD *cdrom)
436{
437    if (fakeCD) {
438        SDL_SetError (kErrorFakeDevice);
439        return -1;
440    }
441
442    Lock ();
443
444    if (PauseFile () < 0) {
445        Unlock ();
446        return -2;
447    }
448
449    if (ReleaseFile () < 0) {
450        Unlock ();
451        return -3;
452    }
453
454    status = CD_STOPPED;
455
456    Unlock ();
457
458    return 0;
459}
460
461/* Eject the CD-ROM (Unmount the volume) */
462static int SDL_SYS_CDEject(SDL_CD *cdrom)
463{
464    OSStatus err;
465    pid_t dissenter;
466
467    if (fakeCD) {
468        SDL_SetError (kErrorFakeDevice);
469        return -1;
470    }
471
472    Lock ();
473
474    if (PauseFile () < 0) {
475        Unlock ();
476        return -2;
477    }
478
479    if (ReleaseFile () < 0) {
480        Unlock ();
481        return -3;
482    }
483
484    status = CD_STOPPED;
485
486	/* Eject the volume */
487	err = FSEjectVolumeSync(volumes[cdrom->id], kNilOptions, &dissenter);
488
489	if (err != noErr) {
490        Unlock ();
491		SDL_SetError ("PBUnmountVol returned %d", err);
492		return -4;
493	}
494
495    status = CD_TRAYEMPTY;
496
497    /* Invalidate volume and track info */
498    volumes[cdrom->id] = 0;
499    free (tracks[cdrom->id]);
500    tracks[cdrom->id] = NULL;
501
502    Unlock ();
503
504    return 0;
505}
506
507/* Close the CD-ROM */
508static void SDL_SYS_CDClose(SDL_CD *cdrom)
509{
510    currentDrive = -1;
511    return;
512}
513
514#endif /* SDL_CDROM_MACOSX */
515