File indexing completed on 2022-11-29 14:10:18

0001 /*
0002  * This file is part of WorkMan, the civilized CD player library
0003  * Copyright (C) 1991-1997 by Steven Grimm (original author)
0004  * Copyright (C) by Dirk Försterling <milliByte@DeathsDoor.com>
0005  * Copyright (C) 2004-2006 Alexander Kern <alex.kern@gmx.de>
0006  *
0007  * This library is free software; you can redistribute it and/or
0008  * modify it under the terms of the GNU Library General Public
0009  * License as published by the Free Software Foundation; either
0010  * version 2 of the License, or (at your option) any later version.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Library General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Library General Public
0018  * License along with this library; if not, write to the Free
0019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0020  *
0021  *
0022  * Interface between most of WorkMan and the low-level CD-ROM library
0023  * routines defined in plat_*.c and drv_*.c.  The goal is to have no
0024  * platform- or drive-dependent code here.
0025  */
0026 
0027 #define _BSD_SOURCE /* strdup */
0028 #define _DEFAULT_SOURCE /* stop glibc whining about the previous line */
0029 
0030 #include "include/wm_config.h"
0031 #include "include/wm_struct.h"
0032 #include "include/wm_cddb.h"
0033 #include "include/wm_cdrom.h"
0034 #include "include/wm_platform.h"
0035 #include "include/wm_helpers.h"
0036 #include "include/wm_cdtext.h"
0037 #include "include/wm_scsi.h"
0038 
0039 #include <errno.h>
0040 #include <stdio.h>
0041 #include <unistd.h>
0042 #include <stdlib.h>
0043 #include <string.h>
0044 #include <strings.h>
0045 #include <sys/types.h>
0046 
0047 #ifdef CAN_CLOSE
0048 #include <fcntl.h>
0049 #endif
0050 
0051 /* local prototypes */
0052 static int fixup_drive_struct(struct wm_drive *);
0053 static int read_toc(struct wm_drive *);
0054 static const char* gen_status(int);
0055 
0056 #define WM_MSG_CLASS WM_MSG_CLASS_CDROM
0057 
0058 /* extern struct wm_drive generic_proto, toshiba_proto, sony_proto; */
0059 /*  toshiba33_proto; <=== Somehow, this got lost */
0060 
0061 /*
0062  * The supported drive types are listed here.  NULL means match anything.
0063  * The first match in the list is used, and substring matches are done (so
0064  * put long names before their shorter prefixes.)
0065  */
0066 static struct drivelist {
0067   const char *vendor;
0068   const char *model;
0069   const char *revision;
0070   int (*fixup)(struct wm_drive *d);
0071 } drives[] = {
0072 {   "TOSHIBA",      "XM-3501",      NULL,       toshiba_fixup   },
0073 {   "TOSHIBA",      "XM-3401",      NULL,       toshiba_fixup   },
0074 {   "TOSHIBA",      "XM-3301",      NULL,       toshiba_fixup   },
0075 {   "SONY",         "CDU-8012",     NULL,       sony_fixup  },
0076 {   "SONY",         "CDU 561",      NULL,       sony_fixup  },
0077 {       "SONY",             "CDU-76S",          NULL,       sony_fixup  },
0078 {   NULL,           NULL,           NULL,       NULL    }
0079 };
0080 
0081 /*
0082  * Solaris 2.2 will remove the device out from under us.  Getting an ENOENT
0083  * is therefore sometimes not a problem.
0084  */
0085 int intermittent_dev = 0;
0086 
0087 
0088 /*
0089  * Macro magic
0090  *
0091  */
0092 
0093 #define CARRAY(id) ((id)-1)
0094 
0095 #define DATATRACK 1
0096 
0097 /*
0098  * get the current position
0099  */
0100 int wm_get_cur_pos_rel(void *p)
0101 {
0102     struct wm_drive *pdrive = (struct wm_drive *)p;
0103     return pdrive->thiscd.cur_pos_rel;
0104 }
0105 
0106 /*
0107  * get the current position
0108  */
0109 int wm_get_cur_pos_abs(void *p)
0110 {
0111     struct wm_drive *pdrive = (struct wm_drive *)p;
0112     return pdrive->thiscd.cur_pos_abs;
0113 }
0114 
0115 /*
0116  * init the workmanlib
0117  */
0118 int wm_cd_init(const char *cd_device, const char *soundsystem,
0119   const char *sounddevice, const char *ctldevice, void **ppdrive)
0120 {
0121     int err;
0122     struct wm_drive *pdrive;
0123 
0124     if(!ppdrive)
0125         return -1;
0126 
0127     pdrive = *ppdrive = (struct wm_drive *)malloc(sizeof(struct wm_drive));
0128     if(!pdrive)
0129         return -1;
0130     memset(pdrive, 0, sizeof(*pdrive));
0131 
0132     pdrive->cdda = (soundsystem && strcasecmp(soundsystem, "cdin"));
0133 
0134     pdrive->cd_device = cd_device ? strdup(cd_device) : strdup(DEFAULT_CD_DEVICE);
0135     pdrive->soundsystem = soundsystem ? strdup(soundsystem): NULL;
0136     pdrive->sounddevice = sounddevice ? strdup(sounddevice) : NULL;
0137     pdrive->ctldevice = ctldevice ? strdup(ctldevice) : NULL;
0138     if(!pdrive->cd_device) {
0139         err = -ENOMEM;
0140         goto init_failed;
0141     }
0142     pdrive->fd = -1;
0143 
0144     pdrive->proto.open = gen_open;
0145     pdrive->proto.close = gen_close;
0146     pdrive->proto.get_trackcount = gen_get_trackcount;
0147     pdrive->proto.get_cdlen = gen_get_cdlen;
0148     pdrive->proto.get_trackinfo = gen_get_trackinfo;
0149     pdrive->proto.get_drive_status = gen_get_drive_status;
0150     pdrive->proto.pause = gen_pause;
0151     pdrive->proto.resume = gen_resume;
0152     pdrive->proto.stop = gen_stop;
0153     pdrive->proto.play = gen_play;
0154     pdrive->proto.eject = gen_eject;
0155     pdrive->proto.closetray = gen_closetray;
0156     pdrive->proto.scsi = gen_scsi;
0157     pdrive->proto.set_volume = gen_set_volume;
0158     pdrive->proto.get_volume = gen_get_volume;
0159     pdrive->proto.scale_volume = gen_scale_volume;
0160     pdrive->proto.unscale_volume = gen_unscale_volume;
0161     pdrive->oldmode = WM_CDM_UNKNOWN;
0162 
0163     if((err = gen_init(pdrive)) < 0)
0164         goto init_failed;
0165 
0166     if ((err = pdrive->proto.open(pdrive)) < 0)
0167         goto open_failed;
0168 
0169     /* Can we figure out the drive type? */
0170     if (wm_scsi_get_drive_type(pdrive)) {
0171         wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "plat_open(): inquiry failed\n");
0172     }
0173 
0174     /* let it override some functions */
0175     fixup_drive_struct(pdrive);
0176 #ifdef WMLIB_CDDA_BUILD
0177     if(pdrive->cdda && (err = wm_cdda_init(pdrive)))
0178         goto open_failed;
0179 #endif
0180     return wm_cd_status(pdrive);
0181 
0182 open_failed:
0183     wm_cd_destroy(pdrive);
0184 
0185 init_failed:
0186     free(pdrive->cd_device);
0187     free(pdrive->soundsystem);
0188     free(pdrive->sounddevice);
0189     free(pdrive->ctldevice);
0190     free(pdrive);
0191 
0192     return err;
0193 }
0194 
0195 int wm_cd_destroy(void *p)
0196 {
0197     struct wm_drive *pdrive = (struct wm_drive *)p;
0198     free_cdtext();
0199 
0200     if(pdrive->cdda)
0201         wm_cdda_destroy(pdrive);
0202 
0203     pdrive->proto.close(pdrive);
0204 
0205     return 0;
0206 }
0207 /*
0208  * Give information about the drive we found during wmcd_open()
0209  */
0210 const char *wm_drive_vendor(void *p)
0211 {
0212     struct wm_drive *pdrive = (struct wm_drive *)p;
0213     return pdrive->vendor?pdrive->vendor:"";
0214 }
0215 
0216 const char *wm_drive_model(void *p)
0217 {
0218     struct wm_drive *pdrive = (struct wm_drive *)p;
0219     return pdrive->model?pdrive->model:"";
0220 }
0221 
0222 const char *wm_drive_revision(void *p)
0223 {
0224     struct wm_drive *pdrive = (struct wm_drive *)p;
0225     return pdrive->revision?pdrive->revision:"";
0226 }
0227 
0228 const char *wm_drive_default_device()
0229 {
0230     return DEFAULT_CD_DEVICE;
0231 }
0232 
0233 unsigned long wm_cddb_discid(void *p)
0234 {
0235     struct wm_drive *pdrive = (struct wm_drive *)p;
0236     return cddb_discid(pdrive);
0237 }
0238 
0239 /*
0240  * Figure out which prototype drive structure we should be using based
0241  * on the vendor, model, and revision of the current pdrive->
0242  */
0243 static int fixup_drive_struct(struct wm_drive *d)
0244 {
0245     struct drivelist *driver;
0246 
0247     for (driver = drives; driver->vendor; driver++) {
0248         if((strncmp(driver->vendor, d->vendor, strlen(d->vendor))) ||
0249             ((driver->model != NULL) && strncmp(driver->model, d->model, strlen(d->model))) ||
0250             ((d->revision != NULL) && strncmp(driver->revision, d->revision, strlen(d->revision))))
0251             continue;
0252 
0253         if(!(driver->fixup))
0254             goto fail;
0255 
0256         driver->fixup(d);
0257         return 0;
0258     }
0259 
0260 fail:
0261     return -1;
0262 } /* find_drive_struct() */
0263 
0264 /*
0265  * read_toc()
0266  *
0267  * Read the table of contents from the CD.  Return a pointer to a wm_cdinfo
0268  * struct containing the relevant information (minus artist/cdname/etc.)
0269  * This is a static struct.  Returns NULL if there was an error.
0270  *
0271  * XXX allocates one trackinfo too many.
0272  */
0273 static int read_toc(struct wm_drive *pdrive)
0274 {
0275     int    i;
0276     int    pos;
0277 
0278     if(!pdrive->proto.get_trackcount ||
0279         pdrive->proto.get_trackcount(pdrive, &pdrive->thiscd.ntracks) < 0) {
0280         return -1 ;
0281     }
0282 
0283     pdrive->thiscd.length = 0;
0284     pdrive->thiscd.cur_cdmode = WM_CDM_UNKNOWN;
0285     pdrive->thiscd.cd_cur_balance = WM_BALANCE_SYMMETRED;
0286 
0287     if (pdrive->thiscd.trk != NULL)
0288         free(pdrive->thiscd.trk);
0289 
0290     pdrive->thiscd.trk = malloc((pdrive->thiscd.ntracks + 1) * sizeof(struct wm_trackinfo));
0291     if (pdrive->thiscd.trk == NULL) {
0292         perror("malloc");
0293         return -1;
0294     }
0295 
0296     for (i = 0; i < pdrive->thiscd.ntracks; i++) {
0297         if(!pdrive->proto.get_trackinfo ||
0298             pdrive->proto.get_trackinfo(pdrive, i + 1, &pdrive->thiscd.trk[i].data,
0299             &pdrive->thiscd.trk[i].start) < 0) {
0300             return -1;
0301         }
0302 
0303         pdrive->thiscd.trk[i].length = pdrive->thiscd.trk[i].start / 75;
0304 
0305         pdrive->thiscd.trk[i].track = i + 1;
0306         wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "track %i, start frame %i\n",
0307             pdrive->thiscd.trk[i].track, pdrive->thiscd.trk[i].start);
0308     }
0309 
0310     if(!pdrive->proto.get_cdlen ||
0311         pdrive->proto.get_cdlen(pdrive, &pdrive->thiscd.trk[i].start) < 0) {
0312         return -1;
0313     }
0314     pdrive->thiscd.trk[i].length = pdrive->thiscd.trk[i].start / 75;
0315 
0316     /* Now compute actual track lengths. */
0317     pos = pdrive->thiscd.trk[0].length;
0318     for (i = 0; i < pdrive->thiscd.ntracks; i++) {
0319         pdrive->thiscd.trk[i].length = pdrive->thiscd.trk[i+1].length - pos;
0320         pos = pdrive->thiscd.trk[i+1].length;
0321         if (pdrive->thiscd.trk[i].data)
0322             pdrive->thiscd.trk[i].length = (pdrive->thiscd.trk[i + 1].start - pdrive->thiscd.trk[i].start) * 2;
0323     }
0324 
0325     pdrive->thiscd.length = pdrive->thiscd.trk[pdrive->thiscd.ntracks].length;
0326 
0327     wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "read_toc() successful\n");
0328     return 0;
0329 } /* read_toc() */
0330 
0331 /*
0332  * wm_cd_status(pdrive)
0333  *
0334  * Return values:
0335  *     see wm_cdrom.h
0336  *
0337  * Updates variables.
0338  */
0339 int wm_cd_status(void *p)
0340 {
0341     struct wm_drive *pdrive = (struct wm_drive *)p;
0342     int mode = -1, tmp;
0343 
0344     if(!pdrive->proto.get_drive_status ||
0345         (tmp = pdrive->proto.get_drive_status(pdrive, pdrive->oldmode, &mode,
0346         &pdrive->thiscd.cur_frame,
0347         &pdrive->thiscd.curtrack,
0348         &pdrive->thiscd.cur_index)) < 0) {
0349         perror("WM get_drive_status");
0350         return -1;
0351     }
0352 
0353     wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS,
0354         "get_drive_status returns status %s, track %i, frame %i\n",
0355         gen_status(mode), pdrive->thiscd.curtrack, pdrive->thiscd.cur_frame);
0356 
0357     if(WM_CDS_NO_DISC(pdrive->oldmode) && WM_CDS_DISC_READY(mode)) {
0358         /* device changed */
0359         pdrive->thiscd.ntracks = 0;
0360 
0361         if(read_toc(pdrive) || 0 == pdrive->thiscd.ntracks) {
0362 
0363             mode = WM_CDM_NO_DISC;
0364         } else /* refresh cdtext info */
0365             get_glob_cdtext(pdrive, 1);
0366 
0367         wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS,
0368             "device status changed() from %s to %s\n",
0369             gen_status(pdrive->oldmode), gen_status(mode));
0370     }
0371     pdrive->oldmode = mode;
0372 
0373     /*
0374     * it seems all driver have'nt state for stop
0375     */
0376     if(WM_CDM_PAUSED == mode && 0 == pdrive->thiscd.cur_frame) {
0377         mode = WM_CDM_STOPPED;
0378         pdrive->thiscd.curtrack = 0;
0379     }
0380 
0381     switch (mode) {
0382     case WM_CDM_PLAYING:
0383     case WM_CDM_PAUSED:
0384         pdrive->thiscd.cur_pos_abs = pdrive->thiscd.cur_frame / 75;
0385         /* search for right track */
0386         for(tmp = pdrive->thiscd.ntracks;
0387             tmp > 1 && pdrive->thiscd.cur_frame < pdrive->thiscd.trk[CARRAY(tmp)].start;
0388             tmp--)
0389             ;
0390         pdrive->thiscd.curtrack = tmp;
0391         /* Fall through */
0392 
0393 
0394     case WM_CDM_UNKNOWN:
0395         if (mode == WM_CDM_UNKNOWN)
0396         {
0397             mode = WM_CDM_NO_DISC;
0398         }
0399         /* Fall through */
0400 
0401     case WM_CDM_STOPPED:
0402 /*assert(thiscd.trk != NULL);*/
0403         if(pdrive->thiscd.curtrack >= 1 && pdrive->thiscd.curtrack <= pdrive->thiscd.ntracks) {
0404             pdrive->thiscd.cur_pos_rel = (pdrive->thiscd.cur_frame -
0405                 pdrive->thiscd.trk[CARRAY(pdrive->thiscd.curtrack)].start) / 75;
0406             if (pdrive->thiscd.cur_pos_rel < 0)
0407                 pdrive->thiscd.cur_pos_rel = -pdrive->thiscd.cur_pos_rel;
0408         }
0409 
0410         if (pdrive->thiscd.cur_pos_abs < 0)
0411             pdrive->thiscd.cur_pos_abs = pdrive->thiscd.cur_frame = 0;
0412 
0413         if (pdrive->thiscd.curtrack < 1)
0414             pdrive->thiscd.curtracklen = pdrive->thiscd.length;
0415         else
0416             pdrive->thiscd.curtracklen = pdrive->thiscd.trk[CARRAY(pdrive->thiscd.curtrack)].length;
0417         /* Fall through */
0418 
0419     case WM_CDM_TRACK_DONE:
0420         pdrive->thiscd.cur_cdmode = mode;
0421         break;
0422     case WM_CDM_FORWARD:
0423     case WM_CDM_EJECTED:
0424         pdrive->thiscd.cur_cdmode = mode;
0425         break;
0426     }
0427 
0428     wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS,
0429         "wm_cd_status returns %s\n", gen_status(pdrive->thiscd.cur_cdmode));
0430 
0431     return pdrive->thiscd.cur_cdmode;
0432 }
0433 
0434 int wm_cd_getcurtrack(void *p)
0435 {
0436     struct wm_drive *pdrive = (struct wm_drive *)p;
0437     if(WM_CDS_NO_DISC(pdrive->thiscd.cur_cdmode))
0438         return 0;
0439     return pdrive->thiscd.curtrack;
0440 }
0441 
0442 int wm_cd_getcurtracklen(void *p)
0443 {
0444     struct wm_drive *pdrive = (struct wm_drive *)p;
0445     if(WM_CDS_NO_DISC(pdrive->thiscd.cur_cdmode))
0446         return 0;
0447 
0448     return pdrive->thiscd.curtracklen;
0449 }
0450 
0451 int wm_cd_getcountoftracks(void *p)
0452 {
0453     struct wm_drive *pdrive = (struct wm_drive *)p;
0454     if(WM_CDS_NO_DISC(pdrive->thiscd.cur_cdmode))
0455         return 0;
0456 
0457     return pdrive->thiscd.ntracks;
0458 }
0459 
0460 int wm_cd_gettracklen(void *p, int track)
0461 {
0462     struct wm_drive *pdrive = (struct wm_drive *)p;
0463     if (track < 1 ||
0464         track > pdrive->thiscd.ntracks ||
0465         pdrive->thiscd.trk == NULL)
0466         return 0;
0467 
0468     return pdrive->thiscd.trk[CARRAY(track)].length;
0469 }
0470 
0471 int wm_cd_gettrackstart(void *p, int track)
0472 {
0473     struct wm_drive *pdrive = (struct wm_drive *)p;
0474     if (track < 1 ||
0475         track > (pdrive->thiscd.ntracks+1) ||
0476         pdrive->thiscd.trk == NULL)
0477         return 0;
0478 
0479   return pdrive->thiscd.trk[CARRAY(track)].start;
0480 }
0481 
0482 int wm_cd_gettrackdata(void *p, int track)
0483 {
0484     struct wm_drive *pdrive = (struct wm_drive *)p;
0485     if (track < 1 ||
0486         track > pdrive->thiscd.ntracks ||
0487         pdrive->thiscd.trk == NULL)
0488         return 0;
0489 
0490   return pdrive->thiscd.trk[CARRAY(track)].data;
0491 }
0492 
0493 /*
0494  * wm_cd_play(starttrack, pos, endtrack)
0495  *
0496  * Start playing the CD or jump to a new position.  "pos" is in seconds,
0497  * relative to start of track.
0498  */
0499 int wm_cd_play(void *p, int start, int pos, int end)
0500 {
0501     struct wm_drive *pdrive = (struct wm_drive *)p;
0502     int real_start, real_end, status;
0503     int play_start, play_end;
0504 
0505     status = wm_cd_status(pdrive);
0506     if(WM_CDS_NO_DISC(status) || pdrive->thiscd.ntracks < 1)
0507         return -1;
0508 
0509     /*
0510     * check ranges
0511     */
0512     for(real_end = pdrive->thiscd.ntracks;
0513         (pdrive->thiscd.trk[CARRAY(real_end)].data == DATATRACK);
0514         real_end--)
0515         ;
0516     for(real_start = 1;
0517         (pdrive->thiscd.trk[CARRAY(real_start)].data == DATATRACK);
0518         real_start++)
0519         ;
0520 
0521     if(end == WM_ENDTRACK)
0522         end = real_end;
0523     else if(end > real_end)
0524         end = real_end;
0525 
0526     /*
0527     * handle as overrun
0528     */
0529     if(start < real_start)
0530         start = real_start;
0531     if(start > real_end)
0532         start = real_end;
0533 
0534     /*
0535     * Try to avoid mixed mode and CD-EXTRA data tracks
0536     */
0537     if(start > end || pdrive->thiscd.trk[CARRAY(start)].data == DATATRACK) {
0538         wm_cd_stop(pdrive);
0539         return -1;
0540     }
0541 
0542     play_start = pdrive->thiscd.trk[CARRAY(start)].start + pos * 75;
0543     play_end = (end == pdrive->thiscd.ntracks) ? pdrive->thiscd.length * 75 :
0544         pdrive->thiscd.trk[CARRAY(end)].start - 1;
0545 
0546     --play_end;
0547 
0548     if (play_start >= play_end)
0549         play_start = play_end-1;
0550 
0551     if(pdrive->proto.play)
0552         pdrive->proto.play(pdrive, play_start, play_end);
0553     else
0554         return -1;
0555 
0556         /* So we don't update the display with the old frame number */
0557     wm_cd_status(pdrive);
0558 
0559     return pdrive->thiscd.curtrack;
0560 }
0561 
0562 /*
0563  * wm_cd_pause()
0564  *
0565  * Pause the CD, if it's in play mode.  If it's already paused, go back to
0566  * play mode.
0567  */
0568 int wm_cd_pause(void *p)
0569 {
0570     struct wm_drive *pdrive = (struct wm_drive *)p;
0571     static int paused_pos;
0572     int status;
0573 
0574     status = wm_cd_status(pdrive);
0575     if(WM_CDS_NO_DISC(status))
0576         return -1;
0577 
0578     if(WM_CDM_PLAYING == pdrive->thiscd.cur_cdmode) {
0579         paused_pos = pdrive->thiscd.cur_pos_rel;
0580         if(pdrive->proto.pause)
0581             return pdrive->proto.pause(pdrive);
0582     } else if(WM_CDM_PAUSED == status) {
0583         if(pdrive->proto.resume)
0584             return pdrive->proto.resume(pdrive);
0585         else if(pdrive->proto.play)
0586             return pdrive->proto.play(pdrive, pdrive->thiscd.cur_pos_rel, -1);
0587     }
0588 
0589     return -1;
0590 } /* wm_cd_pause() */
0591 
0592 /*
0593  * wm_cd_stop()
0594  *
0595  * Stop the CD if it's not already stopped.
0596  */
0597 int wm_cd_stop(void *p)
0598 {
0599     struct wm_drive *pdrive = (struct wm_drive *)p;
0600     int status;
0601 
0602     status = wm_cd_status(pdrive);
0603     if(WM_CDS_NO_DISC(status))
0604         return -1;
0605 
0606     if (status != WM_CDM_STOPPED) {
0607 
0608         if(pdrive->proto.stop)
0609             pdrive->proto.stop(pdrive);
0610 
0611         status = wm_cd_status(pdrive);
0612     }
0613 
0614     return (status != WM_CDM_STOPPED);
0615 } /* wm_cd_stop() */
0616 
0617 /*
0618  * Eject the current CD, if there is one, and set the mode to 5.
0619  *
0620  * Returns 0 on success, 1 if the CD couldn't be ejected, or 2 if the
0621  * CD contains a mounted filesystem.
0622  */
0623 int wm_cd_eject(void *p)
0624 {
0625     struct wm_drive *pdrive = (struct wm_drive *)p;
0626     int err = -1;
0627 
0628     if(pdrive->proto.eject)
0629         err = pdrive->proto.eject(pdrive);
0630 
0631     if (err < 0) {
0632         if (err == -3) {
0633             return 2;
0634         } else {
0635             return 1;
0636         }
0637     }
0638 
0639     return (WM_CDM_EJECTED == wm_cd_status(pdrive)) ? 0 : -1;
0640 }
0641 
0642 int wm_cd_closetray(void *p)
0643 {
0644     struct wm_drive *pdrive = (struct wm_drive *)p;
0645     int status, err = -1;
0646 
0647     status = wm_cd_status(pdrive);
0648     if (status == WM_CDM_UNKNOWN || status == WM_CDM_NO_DISC)
0649         return -1;
0650 
0651 #ifdef CAN_CLOSE
0652     err = pdrive->proto.closetray(pdrive);
0653 
0654     if(err) {
0655         /* do close/open */
0656         if(!pdrive->proto.close(pdrive)) {
0657             wm_susleep( 1000 );
0658             err = pdrive->proto.open(pdrive);
0659             wm_susleep( 1000 );
0660         }
0661     }
0662 
0663 #else
0664     err = 0;
0665 #endif
0666 
0667     return (err ? 0 : ((wm_cd_status(pdrive) == 2) ? 1 : 0));
0668 } /* wm_cd_closetray() */
0669 
0670 struct cdtext_info* wm_cd_get_cdtext(void *p)
0671 {
0672     struct wm_drive *pdrive = (struct wm_drive *)p;
0673     int status;
0674 
0675     status = wm_cd_status(pdrive);
0676 
0677     if(WM_CDS_NO_DISC(status))
0678         return NULL;
0679 
0680     return get_glob_cdtext(pdrive, 0);
0681 }
0682 
0683 int wm_cd_set_verbosity(int level)
0684 {
0685     wm_lib_set_verbosity(level);
0686     return wm_lib_get_verbosity();
0687 }
0688 
0689 /*
0690  * volume is valid WM_VOLUME_MUTE <= vol <= WM_VOLUME_MAXIMAL,
0691  * balance is valid WM_BALANCE_ALL_LEFTS <= balance <= WM_BALANCE_ALL_RIGHTS
0692  */
0693 
0694 int wm_cd_volume(void *p, int vol, int bal)
0695 {
0696     struct wm_drive *pdrive = (struct wm_drive *)p;
0697     int left, right;
0698     const int bal1 = (vol - WM_VOLUME_MUTE)/(WM_BALANCE_ALL_RIGHTS - WM_BALANCE_SYMMETRED);
0699 
0700 /*
0701  * Set "left" and "right" to volume-slider values accounting for the
0702  * balance setting.
0703  *
0704  */
0705     if(vol < WM_VOLUME_MUTE)
0706         vol = WM_VOLUME_MUTE;
0707     if(vol > WM_VOLUME_MAXIMAL)
0708         vol = WM_VOLUME_MAXIMAL;
0709     if(bal < WM_BALANCE_ALL_LEFTS)
0710         bal = WM_BALANCE_ALL_LEFTS;
0711     if(bal > WM_BALANCE_ALL_RIGHTS)
0712         bal = WM_BALANCE_ALL_RIGHTS;
0713 
0714     left = vol - (bal * bal1);
0715     right = vol + (bal * bal1);
0716 
0717     wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "calculate volume left %i, right %i\n", left, right);
0718 
0719     if (left > WM_VOLUME_MAXIMAL)
0720         left = WM_VOLUME_MAXIMAL;
0721     if (right > WM_VOLUME_MAXIMAL)
0722         right = WM_VOLUME_MAXIMAL;
0723 
0724     if(pdrive->proto.scale_volume)
0725         pdrive->proto.scale_volume(&left, &right);
0726 
0727     if(pdrive->proto.set_volume)
0728         return pdrive->proto.set_volume(pdrive, left, right);
0729 
0730     return -1;
0731 } /* cd_volume() */
0732 
0733 int wm_cd_getvolume(void *p)
0734 {
0735     struct wm_drive *pdrive = (struct wm_drive *)p;
0736     int left, right;
0737 
0738     if(!pdrive->proto.get_volume ||
0739         pdrive->proto.get_volume(pdrive, &left, &right) < 0 || left == -1)
0740         return -1;
0741 
0742     if(pdrive->proto.unscale_volume)
0743         pdrive->proto.unscale_volume(&left, &right);
0744 
0745     if (left < right) {
0746         pdrive->thiscd.cd_cur_balance = (right - left) / 2;
0747         if (pdrive->thiscd.cd_cur_balance > WM_BALANCE_ALL_RIGHTS)
0748             pdrive->thiscd.cd_cur_balance = WM_BALANCE_ALL_RIGHTS;
0749         return right;
0750     } else if (left == right) {
0751         pdrive->thiscd.cd_cur_balance = WM_BALANCE_SYMMETRED;
0752         return left;
0753     } else {
0754         pdrive->thiscd.cd_cur_balance = (right - left) / 2;
0755         if (pdrive->thiscd.cd_cur_balance < WM_BALANCE_ALL_LEFTS)
0756             pdrive->thiscd.cd_cur_balance = WM_BALANCE_ALL_LEFTS;
0757         return left;
0758     }
0759 }
0760 
0761 int wm_cd_getbalance(void *p)
0762 {
0763     struct wm_drive *pdrive = (struct wm_drive *)p;
0764     int left, right;
0765 
0766     if(!pdrive->proto.get_volume ||
0767         pdrive->proto.get_volume(pdrive, &left, &right) < 0 || left == -1)
0768         return WM_BALANCE_SYMMETRED;
0769 
0770     if(pdrive->proto.unscale_volume)
0771         pdrive->proto.unscale_volume(&left, &right);
0772 
0773     if (left < right) {
0774         pdrive->thiscd.cd_cur_balance = (right - left) / 2;
0775         if (pdrive->thiscd.cd_cur_balance > WM_BALANCE_ALL_RIGHTS)
0776             pdrive->thiscd.cd_cur_balance = WM_BALANCE_ALL_RIGHTS;
0777     } else if (left == right) {
0778         pdrive->thiscd.cd_cur_balance = WM_BALANCE_SYMMETRED;
0779     } else {
0780         pdrive->thiscd.cd_cur_balance = (right - left) / 2;
0781         if (pdrive->thiscd.cd_cur_balance < WM_BALANCE_ALL_LEFTS)
0782             pdrive->thiscd.cd_cur_balance = WM_BALANCE_ALL_LEFTS;
0783     }
0784     return pdrive->thiscd.cd_cur_balance;
0785 }
0786 
0787 static const char *gen_status(int status)
0788 {
0789     static char tmp[250];
0790 
0791     switch(status) {
0792     case WM_CDM_TRACK_DONE:
0793         return "WM_CDM_TRACK_DONE";
0794     case WM_CDM_PLAYING:
0795         return "WM_CDM_PLAYING";
0796     case WM_CDM_FORWARD:
0797         return "WM_CDM_FORWARD";
0798     case WM_CDM_PAUSED:
0799         return "WM_CDM_PAUSED";
0800     case WM_CDM_STOPPED:
0801         return "WM_CDM_STOPPED";
0802     case WM_CDM_EJECTED:
0803         return "WM_CDM_EJECTED";
0804     case WM_CDM_DEVICECHANGED:
0805         return "WM_CDM_DEVICECHANGED";
0806     case WM_CDM_NO_DISC:
0807         return "WM_CDM_NO_DISC";
0808     case WM_CDM_UNKNOWN:
0809         return "WM_CDM_UNKNOWN";
0810     case WM_CDM_CDDAERROR:
0811         return "WM_CDM_CDDAERROR";
0812     case WM_CDM_CDDAACK:
0813         return "WM_CDM_CDDAACK";
0814     case WM_CDM_LOADING:
0815         return "WM_CDM_LOADING";
0816     case WM_CDM_BUFFERING:
0817         return "WM_CDM_BUFFERING";
0818     default:
0819         sprintf(tmp, "unexpected status %i", status);
0820         return tmp;
0821     }
0822 }