1 /*
2 * $Id: cdrom.c,v 1.10 1999/05/05 16:34:19 dirk Exp $
3 *
4 * This file is part of WorkMan, the civilized CD player library
5 * (c) 1991-1997 by Steven Grimm (original author)
6 * (c) by Dirk F�rsterling (current 'author' = maintainer)
7 * The maintainer can be contacted by his e-mail address:
8 * milliByte@DeathsDoor.com
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public
21 * License along with this library; if not, write to the Free
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 *
25 * Interface between most of WorkMan and the low-level CD-ROM library
26 * routines defined in plat_*.c and drv_*.c. The goal is to have no
27 * platform- or drive-dependent code here.
28 */
29
30 static char cdrom_id[] = "$Id: cdrom.c,v 1.10 1999/05/05 16:34:19 dirk Exp $";
31
32 #include <errno.h>
33 #include <stdio.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/types.h>
38 /* #include <sys/time.h> */
39
40 #include "include/wm_config.h"
41 #include "include/wm_struct.h"
42 #include "include/wm_cddb.h"
43 #include "include/wm_cdrom.h"
44 #include "include/wm_database.h"
45 #include "include/wm_platform.h"
46 #include "include/wm_helpers.h"
47 #include "include/wm_cdinfo.h"
48
49 #ifdef CAN_CLOSE
50 #include <fcntl.h>
51 #endif
52
53 #define WM_MSG_CLASS WM_MSG_CLASS_CDROM
54
55 /* extern struct wm_drive generic_proto, toshiba_proto, sony_proto; */
56 /* toshiba33_proto; <=== Somehow, this got lost */
57
58 /*
59 * The supported drive types are listed here. NULL means match anything.
60 * The first match in the list is used, and substring matches are done (so
61 * put long names before their shorter prefixes.)
62 */
63 struct drivelist {
64 char *ven;
65 char *mod;
66 char *rev;
67 struct wm_drive *proto;
68 } drives[] = {
69 { "TOSHIBA", "XM-3501", NULL, &toshiba_proto },
70 { "TOSHIBA", "XM-3401", NULL, &toshiba_proto },
71 { "TOSHIBA", "XM-3301", NULL, &toshiba_proto },
72 { "SONY", "CDU-8012", NULL, &sony_proto },
73 { "SONY", "CDU 561", NULL, &sony_proto },
74 { WM_STR_GENVENDOR, WM_STR_GENMODEL, WM_STR_GENREV, &generic_proto },
75 { NULL, NULL, NULL, &generic_proto }
76 };
77
78 /*
79 * Solaris 2.2 will remove the device out from under us. Getting an ENOENT
80 * is therefore sometimes not a problem.
81 */
82 int intermittent_dev = 0;
83
84 /*
85 * Do we want to keep the CD device open after quitting by default?
86 *
87 int keep_open = 0;
88 */
89
90 #if defined DEFAULT_CD_DEVICE
91 char *cd_device = DEFAULT_CD_DEVICE;
92 #else
93 char *cd_device = NULL;
94 #endif
95
96
97 int wm_cd_cur_balance = 10;
98
99 struct wm_drive drive = { -1, "", "", "", NULL, NULL };
100
101 char _wm_drive_vendor[32] = "Generic";
102 char _wm_drive_model[32] = "drive type";
103 char _wm_drive_revision[32] = "";
104
105 /*
106 * Give information about the drive we found during wmcd_open()
107 */
108
wm_drive_vendor(void)109 char *wm_drive_vendor( void )
110 {
111 char *s = NULL;
112
113 wm_strmcpy( &s, _wm_drive_vendor );
114 return s;
115 }
116
wm_drive_model(void)117 char *wm_drive_model( void )
118 {
119 char *s = NULL;
120
121 wm_strmcpy( &s, _wm_drive_model );
122 return s;
123 }
124
wm_drive_revision(void)125 char *wm_drive_revision( void )
126 {
127 char *s = NULL;
128
129 wm_strmcpy( &s, _wm_drive_revision );
130 return s;
131 }
132
wm_drive_settype(char * vendor,char * model,char * revision)133 void wm_drive_settype( char *vendor, char *model, char *revision )
134 {
135 sprintf( _wm_drive_vendor, "%s", vendor );
136 sprintf( _wm_drive_model, "%s", model );
137 sprintf( _wm_drive_revision, "%s", revision );
138 }
139
140 /*
141 * Figure out which prototype drive structure we should be using based
142 * on the vendor, model, and revision of the current drive.
143 */
144 struct wm_drive *
find_drive_struct(char * vendor,char * model,char * rev)145 find_drive_struct(char *vendor, char *model, char *rev)
146 {
147 struct drivelist *d;
148
149 for (d = drives; d; d++)
150 {
151 if( ( (d->ven != NULL) && strncmp(d->ven, vendor, strlen(d->ven)) ) ||
152 ( (d->mod != NULL) && strncmp(d->mod, model, strlen(d->mod)) ) ||
153 ( (d->rev != NULL) && strncmp(d->rev, rev, strlen(d->rev)) ) )
154 continue;
155
156 if (d->proto->vendor[0] == '\0')
157 strcpy(d->proto->vendor, vendor);
158 if (d->proto->model[0] == '\0')
159 strcpy(d->proto->model, model);
160
161 return (d->proto);
162 }
163
164 return (NULL); /* this means the list is badly terminated. */
165 } /* find_drive_struct() */
166
167 /*
168 * read_toc()
169 *
170 * Read the table of contents from the CD. Return a pointer to a wm_cdinfo
171 * struct containing the relevant information (minus artist/cdname/etc.)
172 * This is a static struct. Returns NULL if there was an error.
173 *
174 * XXX allocates one trackinfo too many.
175 */
176 struct wm_cdinfo *
read_toc()177 read_toc()
178 {
179 struct wm_playlist *l;
180 int i, pos;
181
182 if ((drive.get_trackcount)(&drive, &thiscd.ntracks) < 0)
183 {
184 perror("trackcount");
185 return (NULL);
186 }
187
188 thiscd.artist[0] = thiscd.cdname[0] = '\0';
189 thiscd.whichdb = thiscd.otherrc = thiscd.otherdb = thiscd.user = NULL;
190 thiscd.length = 0;
191 thiscd.autoplay = thiscd.playmode = thiscd.volume = 0;
192
193 /* Free up any left-over playlists. */
194 if (thiscd.lists != NULL)
195 {
196 for (l = thiscd.lists; l->name != NULL; l++)
197 {
198 free(l->name);
199 free(l->list);
200 }
201 free(thiscd.lists);
202 thiscd.lists = NULL;
203 }
204
205 if (thiscd.trk != NULL)
206 free(thiscd.trk);
207
208 thiscd.trk = malloc((thiscd.ntracks + 1) * sizeof(struct wm_trackinfo));
209 if (thiscd.trk == NULL)
210 {
211 perror("malloc");
212 return (NULL);
213 }
214
215 for (i = 0; i < thiscd.ntracks; i++)
216 {
217 if ((drive.get_trackinfo)(&drive, i + 1, &thiscd.trk[i].data,
218 &thiscd.trk[i].start) < 0)
219 {
220 perror("CD track info read");
221 return (NULL);
222 }
223
224 thiscd.trk[i].avoid = thiscd.trk[i].data;
225 thiscd.trk[i].length = thiscd.trk[i].start / 75;
226
227 thiscd.trk[i].songname = thiscd.trk[i].otherrc =
228 thiscd.trk[i].otherdb = NULL;
229 thiscd.trk[i].contd = 0;
230 thiscd.trk[i].volume = 0;
231 thiscd.trk[i].track = i + 1;
232 thiscd.trk[i].section = 0;
233 }
234
235 if ((drive.get_cdlen)(&drive, &thiscd.trk[i].start) < 0)
236 {
237 perror("CD length read");
238 return (NULL);
239 }
240 thiscd.trk[i].length = thiscd.trk[i].start / 75;
241
242 /* Now compute actual track lengths. */
243 pos = thiscd.trk[0].length;
244
245 for (i = 0; i < thiscd.ntracks; i++)
246 {
247 thiscd.trk[i].length = thiscd.trk[i+1].length - pos;
248 pos = thiscd.trk[i+1].length;
249 if (thiscd.trk[i].data)
250 thiscd.trk[i].length = (thiscd.trk[i + 1].start -
251 thiscd.trk[i].start) * 2;
252 if (thiscd.trk[i].avoid)
253 wm_strmcpy(&thiscd.trk[i].songname, "DATA TRACK");
254 }
255
256 thiscd.length = thiscd.trk[thiscd.ntracks].length;
257 thiscd.cddbid = cddb_discid(drive);
258
259 return (&thiscd);
260 }
261
262 /*
263 * wm_cd_status()
264 *
265 * Return values:
266 *
267 * 0 No CD in drive.
268 * 1 CD in drive.
269 * 2 CD has just been inserted (TOC has been read)
270 *
271 * Updates cur_track, cur_pos_rel, cur_pos_abs and other variables.
272 */
273 int
wm_cd_status(void)274 wm_cd_status( void )
275 {
276 static enum wm_cd_modes oldmode = WM_CDM_UNKNOWN;
277 enum wm_cd_modes mode;
278 int status, trackno = cur_track;
279 int ret = WM_CDS_DISC_READY;
280
281 /* Open the drive. This returns 1 if the device isn't ready. */
282
283 status = wmcd_open(&drive);
284
285 if (status < 0)
286 return (status);
287 if (status > 0)
288 return (WM_CDS_NO_DISC);
289
290 /* If the user hit the stop button, don't pass PLAYING as oldmode.
291 * Likewise, if we've just started playing, don't remember that
292 * we were stopped before (or the state machine in get_drive_status
293 * can get confused.)
294 */
295 if( (cur_cdmode == WM_CDM_STOPPED) || (cur_cdmode == WM_CDM_PLAYING) )
296 oldmode = cur_cdmode;
297
298 if( (drive.get_drive_status)(&drive, oldmode, &mode, &cur_frame,
299 &trackno, &cur_index) < 0)
300 {
301 perror("CD get drive status");
302 return (-1);
303 }
304 oldmode = mode;
305
306 if (mode == WM_CDM_EJECTED || mode == WM_CDM_UNKNOWN)
307 {
308 cur_cdmode = WM_CDM_EJECTED;
309 cur_track = -1;
310 cur_cdlen = cur_tracklen = 1;
311 cur_pos_abs = cur_pos_rel = cur_frame = 0;
312
313 if (exit_on_eject)
314 exit(0);
315
316 return (WM_CDS_NO_DISC);
317 }
318
319 /* If there wasn't a CD before and there is now, learn about it. */
320 if (cur_cdmode == WM_CDM_EJECTED)
321 {
322 cur_pos_rel = cur_pos_abs = 0;
323
324
325 status = wmcd_reopen( &drive );
326
327 if ((cd = read_toc()) == NULL)
328 {
329 if (exit_on_eject)
330 {
331 exit(-1);
332 } else {
333 return (-1);
334 }
335 }
336
337 cur_nsections = 0;
338 cur_ntracks = cd->ntracks;
339 cur_cdlen = cd->length;
340 load();
341 cur_artist = cd->artist;
342 cur_cdname = cd->cdname;
343 cur_cdmode = WM_CDM_STOPPED;
344 ret = WM_CDS_JUST_INSERTED;
345 }
346
347 switch (mode) {
348 case WM_CDM_PLAYING:
349 case WM_CDM_PAUSED:
350 cur_pos_abs = cur_frame / 75;
351
352 /* Only look up the current track number if necessary. */
353 if (cur_track < 1 || cur_frame < cd->trk[cur_track-1].start ||
354 cur_frame >= (cur_track >= cur_ntracks ?
355 (cur_cdlen + 1) * 75 :
356 cd->trk[cur_track].start))
357 {
358 cur_track = 0;
359 while (cur_track < cur_ntracks && cur_frame >=
360 cd->trk[cur_track].start)
361 cur_track++;
362 }
363 if (cur_track >= 1 && trackno > cd->trk[cur_track-1].track)
364 cur_track++;
365 /* Fall through */
366
367 case WM_CDM_UNKNOWN:
368 if (mode == WM_CDM_UNKNOWN)
369 {
370 mode = WM_CDM_STOPPED;
371 cur_lasttrack = cur_firsttrack = -1;
372 }
373 /* Fall through */
374
375 case WM_CDM_STOPPED:
376 if (cur_track >= 1 && cur_track <= cur_ntracks)
377 {
378 cur_trackname = cd->trk[cur_track-1].songname;
379 cur_avoid = cd->trk[cur_track-1].avoid;
380 cur_contd = cd->trk[cur_track-1].contd;
381 cur_pos_rel = (cur_frame -
382 cd->trk[cur_track-1].start) / 75;
383 if (cur_pos_rel < 0)
384 cur_pos_rel = -cur_pos_rel;
385 }
386
387 if( (playlist != NULL) && playlist[0].start & (cur_listno > 0))
388 {
389 cur_pos_abs -= cd->trk[playlist[cur_listno-1].
390 start - 1].start / 75;
391 cur_pos_abs += playlist[cur_listno-1].starttime;
392 }
393 if (cur_pos_abs < 0)
394 cur_pos_abs = cur_frame = 0;
395
396 if (cur_track < 1)
397 cur_tracklen = cd->length;
398 else
399 cur_tracklen = cd->trk[cur_track-1].length;
400 /* Fall through */
401
402 case WM_CDM_TRACK_DONE:
403 cur_cdmode = mode;
404 break;
405 case WM_CDM_FORWARD:
406 case WM_CDM_EJECTED:
407 break;
408 }
409
410 return (ret);
411 }
412
413 #undef CLIF_VOL
414 #ifdef CLIF_VOL
415 /*
416 * cd_volume(vol, bal, max)
417 *
418 * Set the volume levels. "vol" and "bal" are the volume and balance knob
419 * settings, respectively. "max" is the maximum value of the volume knob
420 * (the balance knob is assumed to always go from 0 to 20.)
421 */
422 void
cd_volume(vol,bal,max)423 cd_volume(vol, bal, max)
424 int vol, bal, max;
425 {
426 int left, right, scale;
427
428 /*
429 * Set "left" and "right" to volume-slider values accounting for the
430 * balance setting.
431 */
432 /* printf("Vol = %d, Bal = %d, Max = %d\n", vol, bal, max);
433 */
434
435 vol = (vol * 100 + max - 16) / max;
436 scale = (vol + 5) / 10;
437
438 if (bal < 9)
439 {
440 right = vol - scale * (10 - bal);
441 #ifdef SYMETRIC_BALANCE
442 left = vol + scale * (10 - bal);
443 #else
444 left = vol;
445 #endif
446 }
447 else if (bal > 11)
448 {
449 #ifdef SYMETRIC_BALANCE
450 right = vol + scale * (bal - 10);
451 #else
452 right = vol;
453 #endif
454 left = vol - scale * (bal - 10);
455 }
456 else
457 left = right = vol;
458
459 /*
460 * some plat_*.c is missing the limitation
461 */
462 left = left < 0 ? 0 : left > 100 ? 100 : left;
463 right = right < 0 ? 0 : right > 100 ? 100 : right;
464 /* printf("Left = %d, Right = %d\n", left, right);
465 */
466 (void) (drive.set_volume)(&drive, left, right);
467 } /* cd_volume() */
468
469 #else
470
471 /*
472 * cd_volume(vol, bal, max)
473 *
474 * Set the volume levels. "vol" and "bal" are the volume and balance knob
475 * settings, respectively. "max" is the maximum value of the volume knob
476 * (the balance knob is assumed to always go from 0 to 20.)
477 */
478 void
cd_volume(int vol,int bal,int max)479 cd_volume( int vol, int bal, int max )
480 {
481 int left, right;
482
483 /*
484 * Set "left" and "right" to volume-slider values accounting for the
485 * balance setting.
486 *
487 * XXX - the maximum volume setting is assumed to be in the 20-30 range.
488 */
489 if (bal < 9)
490 right = vol - (9 - bal) * 2;
491 else
492 right = vol;
493 if (bal > 11)
494 left = vol - (bal - 11) * 2;
495 else
496 left = vol;
497
498 left = (left * 100 + max - 1) / max;
499 right = (right * 100 + max - 1) / max;
500 if (left > 100)
501 left = 100;
502 if (right > 100)
503 right = 100;
504
505 (void) (drive.set_volume)(&drive, left, right);
506 } /* cd_volume() */
507
508 #endif /* CLIF_VOL */
509
510
511 /*
512 * wm_cd_pause()
513 *
514 * Pause the CD, if it's in play mode. If it's already paused, go back to
515 * play mode.
516 */
517 void
wm_cd_pause(void)518 wm_cd_pause( void )
519 {
520 static int paused_pos;
521
522 if (cur_cdmode == WM_CDM_EJECTED) /* do nothing if there's no CD! */
523 return;
524
525 switch (cur_cdmode) {
526 case WM_CDM_PLAYING: /* playing */
527 cur_cdmode = WM_CDM_PAUSED;
528 (drive.pause)(&drive);
529 paused_pos = cur_pos_rel;
530 break;
531
532 case WM_CDM_PAUSED: /* paused */
533 cur_cdmode = WM_CDM_PLAYING;
534 /* (drive.resume)(&drive); */
535 if ((drive.resume)(&drive))
536 wm_cd_play(cur_track, paused_pos,
537 playlist[cur_listno-1].end);
538 default: /* */
539 break;
540 }
541 } /* wm_cd_pause() */
542
543 /*
544 * wm_cd_stop()
545 *
546 * Stop the CD if it's not already stopped.
547 */
548 void
wm_cd_stop(void)549 wm_cd_stop( void )
550 {
551 if (cur_cdmode == WM_CDM_EJECTED)
552 return;
553
554 if (cur_cdmode != WM_CDM_STOPPED)
555 {
556 cur_lasttrack = cur_firsttrack = -1;
557 cur_cdmode = WM_CDM_STOPPED;
558 (drive.stop)(&drive);
559 cur_track = 1;
560 }
561 } /* wm_cd_stop() */
562
563 /*
564 * wm_cd_play_chunk(start, end)
565 *
566 * Play the CD from one position to another (both in frames.)
567 */
568 void
wm_cd_play_chunk(int start,int end,int realstart)569 wm_cd_play_chunk( int start, int end, int realstart )
570 {
571 if (cur_cdmode == WM_CDM_EJECTED || cd == NULL)
572 return;
573
574 end--;
575 if (start >= end)
576 start = end-1;
577
578 (drive.play)(&drive, start, end, realstart);
579 }
580
581 /*
582 * wm_cd_play(starttrack, pos, endtrack)
583 *
584 * Start playing the CD or jump to a new position. "pos" is in seconds,
585 * relative to start of track.
586 */
587 void
wm_cd_play(int start,int pos,int end)588 wm_cd_play( int start, int pos, int end )
589 {
590 if (cur_cdmode == WM_CDM_EJECTED || cd == NULL)
591 return;
592
593 cur_firsttrack = start;
594 start--;
595 end--;
596 cur_lasttrack = end;
597
598 wm_cd_play_chunk(cd->trk[start].start + pos * 75, end >= cur_ntracks ?
599 cur_cdlen * 75 : cd->trk[end].start - 1,
600 cd->trk[start].start);
601
602 /* So we don't update the display with the old frame number */
603 wm_cd_status();
604 cur_frame = cd->trk[start].start + pos * 75;
605 cur_track = cur_firsttrack;
606 cur_cdmode = WM_CDM_PLAYING;
607 }
608
609 /*
610 * Set the offset into the current track and play. -1 means end of track
611 * (i.e., go to next track.)
612 */
613 void
wm_cd_play_from_pos(int pos)614 wm_cd_play_from_pos( int pos )
615 {
616 if (pos == -1)
617 if (cd)
618 pos = cd->trk[cur_track - 1].length - 1;
619 if (cur_cdmode == WM_CDM_PLAYING)
620 wm_cd_play(cur_track, pos, playlist[cur_listno-1].end);
621 } /* wm_cd_play_from_pos() */
622
623 /*
624 * Eject the current CD, if there is one, and set the mode to 5.
625 *
626 * Returns 0 on success, 1 if the CD couldn't be ejected, or 2 if the
627 * CD contains a mounted filesystem.
628 */
629 int
wm_cd_eject(void)630 wm_cd_eject( void )
631 {
632 int status;
633
634 status = (drive.eject)(&drive);
635 if (status < 0)
636 {
637 if (status == -3)
638 {
639 return (2);
640 } else {
641 return (1);
642 }
643 }
644
645 if (exit_on_eject)
646 exit(0);
647
648 cur_track = -1;
649 cur_cdlen = cur_tracklen = 1;
650 cur_pos_abs = cur_pos_rel = cur_frame = 0;
651 cur_cdmode = WM_CDM_EJECTED;
652
653 return (0);
654 }
655
wm_cd_closetray(void)656 int wm_cd_closetray(void)
657 {
658 return((drive.closetray)(&drive) ? 0 : wm_cd_status()==2 ? 1 : 0);
659 } /* wm_cd_closetray() */
660
661 /*
662 * find_trkind(track, index, start)
663 *
664 * Start playing at a particular track and index, optionally using a particular
665 * frame as a starting position. Returns a frame number near the start of the
666 * index mark if successful, 0 if the track/index didn't exist.
667 *
668 * This is made significantly more tedious (though probably easier to port)
669 * by the fact that CDROMPLAYTRKIND doesn't work as advertised. The routine
670 * does a binary search of the track, terminating when the interval gets to
671 * around 10 frames or when the next track is encountered, at which point
672 * it's a fair bet the index in question doesn't exist.
673 */
674 int
find_trkind(int track,int index,int start)675 find_trkind( int track, int index, int start )
676 {
677 int top = 0, bottom, current, interval, ret = 0, i;
678
679 if( cur_cdmode == WM_CDM_EJECTED || cd == NULL )
680 return ( 0 ); /* WARNING: was nothing */
681
682 for (i = 0; i < cur_ntracks; i++)
683 if (cd->trk[i].track == track)
684 break;
685 bottom = cd->trk[i].start;
686
687 for (; i < cur_ntracks; i++)
688 if (cd->trk[i].track > track)
689 break;
690
691 top = i == cur_ntracks ? (cd->length - 1) * 75 : cd->trk[i].start;
692
693 if (start > bottom && start < top)
694 bottom = start;
695
696 current = (top + bottom) / 2;
697 interval = (top - bottom) / 4;
698
699 do {
700 wm_cd_play_chunk(current, current + 75, current);
701
702 if (wm_cd_status() != 1)
703 return (0);
704 while (cur_frame < current)
705 if (wm_cd_status() != 1 || cur_cdmode != WM_CDM_PLAYING)
706 return (0);
707 else
708 wm_susleep(1);
709
710 if (cd->trk[cur_track - 1].track > track)
711 break;
712
713 if (cur_index >= index)
714 {
715 ret = current;
716 current -= interval;
717 }
718 else
719 current += interval;
720 interval /= 2;
721 } while (interval > 2);
722
723 return (ret);
724 } /* find_trkind() */
725
726 /*
727 * Read the initial volume from the drive, if available. Set cur_balance to
728 * the balance level (0-20, 10=centered) and return the proper setting for
729 * the volume knob.
730 *
731 * "max" is the maximum value of the volume knob.
732 */
733 int
wm_cd_read_initial_volume(int max)734 wm_cd_read_initial_volume( int max )
735 {
736 int left, right;
737
738 if ((drive.get_volume)(&drive, &left, &right) < 0 || left == -1)
739 return (max);
740
741 left = (left * max + 99) / 100;
742 right = (right * max + 99) / 100;
743
744 if (left < right)
745 {
746 wm_cd_cur_balance = (right - left) / 2 + 11;
747 if (wm_cd_cur_balance > 20)
748 wm_cd_cur_balance = 20;
749
750 return (right);
751 }
752 else if (left == right)
753 {
754 wm_cd_cur_balance = 10;
755 return (left);
756 }
757 else
758 {
759 wm_cd_cur_balance = (right - left) / 2 + 9;
760 if (wm_cd_cur_balance < 0)
761 wm_cd_cur_balance = 0;
762
763 return (left);
764 }
765 } /* wm_cd_read_initial_volume() */
766
767 /*
768 * Prototype wm_drive structure, with generic functions. The generic functions
769 * will be replaced with drive-specific functions as appropriate once the drive
770 * type has been sensed.
771 */
772 struct wm_drive generic_proto = {
773 -1, /* fd */
774 "Generic\0", /* vendor */
775 "drive type\0 ", /* model */
776 "\0", /* revision */
777 NULL, /* aux */
778 NULL, /* daux */
779
780 gen_init, /* functions... */
781 gen_get_trackcount,
782 gen_get_cdlen,
783 gen_get_trackinfo,
784 gen_get_drive_status,
785 gen_get_volume,
786 gen_set_volume,
787 gen_pause,
788 gen_resume,
789 gen_stop,
790 gen_play,
791 gen_eject,
792 gen_closetray
793 };
794