%{
/*
 *   CDplayer 2.0 - command line Audio CDplayer
 *   Copyright (C) 1993,1994 Mark Buckaway (mark@datasoft.com)
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#include <stdlib.h>

#ifndef COHERENT
#include <mntent.h>
#endif

/*
 * Linux stores cdrom.h in the linux include directory
 * while all others (I'm told) store cdrom.h in the sys include
 * directory
 */
#ifdef LINUX
#include <linux/cdrom.h>
#else
#include <sys/cdrom.h>
#endif

/* 
 * Define MAXPATHLEN for systems that don't have it.
 */
#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

/*
 * NEC CDROM's do strange things so we redefine the ioctl command to use
 * the special nec_ioctl function that came from xcdplayer
 */
#ifdef NEC_CDROM
#define ioctl nec_ioctl
#endif

/*
 * Define constants
 */

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

/*
 * Define the max number of character for the command line arguments.
 * This is used to allocate the buffer for the lexer input routine.
 * 256 should be MORE than enough
 */
#define MAXCMDLINE 256

/*
 * Define the time constant. This is used in calculating CD times.
 * Its equal to 60*75
 */

#define C_TIME 4500

/*
 * Define global variables
 */

char cd_dev[MAXPATHLEN];
char *progname;
char cmdline[MAXCMDLINE];
int cmd_done;
int cmdlen;
int f,i;
struct cdrom_tochdr tochdr;
struct cdrom_tocentry tocentry;
struct cdrom_ti ti;
struct mntent *mnt;
struct cdrom_subchnl subchnl;
struct cdrom_volctrl volctrl;	
unsigned int t_thistrk,t_lasttrk,t_track;
int cdromok;
int tkcd;
/*
 * Function declarations
 */

void initcdrom();
void cd_play(int cmd);
void cd_paused();
void cd_stop();
void cd_subchnl();
void cd_status();
void cd_tocentry(int track);

%}

%union {
    char    *string;     /* string buffer */
    int    cmd;          /* command value */
    int    num;		 /* number value */
}

%token <string> DEVSTRING 
%token <cmd> CMD_PLAY CMD_STOP CMD_INFO CMD_PAUSE CMD_RESUME CMD_STATUS
%token <cmd> CMD_EJECT CMD_VOLUME CMD_SKIP CMD_DEVICE CMD_USAGE CMD_TKCD
%token <num> NUMBER

%type <cmd> command commands
%type <string> devstring
%type <num> number

%start commands
%%

commands:  command
    | commands command
    ;
	       
command: CMD_PLAY
         {
	       /* 
		* Check if we have a requested track to play, and play it,
                * otherwise, play starting at track 1
                */
               initcdrom();
	       ti.cdti_trk0=tochdr.cdth_trk0;
	       cd_play(CMD_PLAY);
         }
     | CMD_PLAY number
	{
	       initcdrom();
	       if (($2<tochdr.cdth_trk0) || ($2>tochdr.cdth_trk1)) {
	           if (!tkcd) printf("Track %d is invalid. Playing from track 1.\n",$2);
	           ti.cdti_trk0=tochdr.cdth_trk0;
		  } else {
	           ti.cdti_trk0=$2;
	       }
	       cd_play(CMD_PLAY);
	}
    | CMD_STOP
	{
               /* 
                * Stop the CD from playing! We must pause the CDROM drive
                * and then stop it otherwise some drives complain.
                */
	       initcdrom();
	       if ((subchnl.cdsc_audiostatus==CDROM_AUDIO_PAUSED) ||
		   (subchnl.cdsc_audiostatus==CDROM_AUDIO_PLAY)) {
	          cd_stop();
	          if (tkcd) {
	             puts("STOP;OK");
		  } else {
	             puts("Audio CD stopped.");
	          }
	        } else {
	          if (tkcd) {
	             puts("STOP;NOPLAY");
		  } else {
	             puts("CD is not currently paused or playing. Command ignored.");
	          }
	      }		  
	}
     | CMD_EJECT
	{
	       initcdrom();
	       cd_stop();
	       if ((ioctl(f,CDROMEJECT))==-1) {
	           if (tkcd) {
	    	       puts("EJECT;ERROR");
		      } else {
           	       perror("ioctl(CDROMEJECT)");
	           }
                   exit(1);
	       }
	       if (tkcd) {
		  puts("EJECT;OK");
		 } else {
	          puts("Audio CD ejected.");
	       }
	}
     | CMD_TKCD
        {
                tkcd=TRUE;
        }
     | CMD_INFO
	{
               /*
		* All this does is grab the total time for the CD and 
                * figures out the time per track as well. It also shows
		* the CDROM status and track in play (if playing).
		* All figures are caluated in frames where one frame equals
		* about 1/75 seconds.
                */
	       initcdrom();
	       if (!tkcd) {
                  printf("Audio CD Information on %s:\n",cd_dev);
	          printf("Total Tracks: %u\n",tochdr.cdth_trk1);
                 } else {
                  printf("INFO;OK;%d,",tochdr.cdth_trk1);
	       }
	       cd_tocentry(CDROM_LEADOUT);
	       if (!tkcd) {
	          printf("Total Playing Time: %u min %u sec\n",
                         tocentry.cdte_addr.msf.minute,
                         tocentry.cdte_addr.msf.second);
                  printf("Track  Time\n");
                 } else {
                  printf("%lu;",
                         (tocentry.cdte_addr.msf.minute*C_TIME)+
                         (tocentry.cdte_addr.msf.second*75) +
			 tocentry.cdte_addr.msf.frame);
               }
               /*
                * Loop through each of the table of contents entries and pull
                * pull some info about each track.
                */
	       for (i=tochdr.cdth_trk0;i<=tochdr.cdth_trk1;i++) {
		    cd_tocentry(i);
		    if (i==tochdr.cdth_trk0) {
                       t_lasttrk=tocentry.cdte_addr.msf.minute * C_TIME +
                                     tocentry.cdte_addr.msf.second * 75 +
				     tocentry.cdte_addr.msf.frame;
                       continue;
                    }
                    t_thistrk=tocentry.cdte_addr.msf.minute * C_TIME +
                                     tocentry.cdte_addr.msf.second * 75 +
				     tocentry.cdte_addr.msf.frame;
                    t_track = t_thistrk - t_lasttrk ;
		    t_lasttrk=t_thistrk;
                    if (tkcd) {
		       printf("%d,%lu;",i-1, t_track);
                      } else {
		       printf("%2d    %2u:%02u\n",i-1, t_track/C_TIME,(t_track%C_TIME)/75);
		    }
	       }
	       cd_tocentry(CDROM_LEADOUT);
	       t_track=(tocentry.cdte_addr.msf.minute*C_TIME+
		       tocentry.cdte_addr.msf.second*(C_TIME)/75+
		       tocentry.cdte_addr.msf.frame)-t_thistrk;
               if (tkcd) {
		   printf("%d,%lu\n",i-1, t_track);
                  } else {
		   printf("%2d    %2u:%02u\n",i-1, t_track/C_TIME,(t_track%C_TIME)/75);
	       }
	       if (!tkcd) cd_status();

	}
     | CMD_PAUSE
	{
               /*
                * Pause a playing Audio CD
                */
	       initcdrom();
               if (subchnl.cdsc_audiostatus!=CDROM_AUDIO_PLAY) {
		     if (tkcd) {
                        puts("PAUSE;NOPLAY");
                       } else {                     
                        printf("Audio CD not current playing. Command ignored.\n");
                     }
                     exit(1);
               }
	       cd_paused();
	       if (tkcd) {
		  printf("PAUSE;OK;%d\n",subchnl.cdsc_trk);
		} else {	       
		  printf("Audio CD paused at %d. Use \"%s resume\" to continue.\n",subchnl.cdsc_trk,progname);
	       }
	}
    | CMD_RESUME
	{
            /*
             * Check if the Audio CD is currently paused, and resume play.
             */
	       initcdrom();
               if (subchnl.cdsc_audiostatus!=CDROM_AUDIO_PAUSED) {
		    if (tkcd) {
			puts("RESUME;NOPAUSE");
		      } else {
                        puts("Audio CD not current paused. Command ignored.");
		    }
                    exit(1);
               }
	       if ((ioctl(f,CDROMRESUME))==-1) {
		    if (tkcd) {
			puts("RESUME;NOPAUSE");
		      } else {
		        perror("ioctl(CDROMRESUME)");
		    }
		    exit(1);
	       }
	       if (tkcd) {
		  printf("RESUME;OK;%d\n",subchnl.cdsc_trk);
		 } else {
                  printf("Audio CD resumed from track %d.\n",subchnl.cdsc_trk);
	       }
	       break;
	}
   | CMD_VOLUME number
	{
               /*
                * Adjust the volume of the CDROM. This is not supported
		* on all drives and Linux drivers                
		*/
	       initcdrom();
               if (subchnl.cdsc_audiostatus!=CDROM_AUDIO_PLAY) {
		    if (tkcd) {
			puts("VOLUME;NOPLAY");
		      } else {
                        puts("Audio CD not current playing. Command ignored.\n");
		     }
                     exit(1);
               }
               if (($2<0) || ($2>255)) {
                  if (!tkcd) puts("Illegal Volume value. Use 0 - 255. Command ignored.\n");
                  break;
               }
               volctrl.channel0=$2;
               volctrl.channel1=$2;
               volctrl.channel2=$2;
               volctrl.channel3=$2;
	       if ((ioctl(f,CDROMVOLCTRL,&volctrl))==-1) {
	          if (tkcd) {
	    	     puts("VOLUME;ERROR");
		  } else {
		     perror("ioctl(CDROMVOLCTRL)");
		  }
		  exit(1);
	       }
	       if (tkcd) {
		  printf("VOLUME;OK;%d\n",$2);
		 } else {	
                  printf("Audio CD Volume set to %d.\n",$2);
	       }
	}
     | CMD_SKIP
	{
	       /*
                * Figure out the current track and skip to the next one.
                * If this is the last track, play the first track.
                */
	       initcdrom();
               if (subchnl.cdsc_audiostatus!=CDROM_AUDIO_PLAY) {
		    if (tkcd) {
			puts("SKIP;NOPLAY");
		      } else {
                        puts("Audio CD not current playing. Command ignored.");
		     }
                     exit(1);
               }
	       if ((ti.cdti_trk0=++subchnl.cdsc_trk)>tochdr.cdth_trk1) {
		  if (tkcd) {
		     puts("SKIP;END");
                    } else {
                     puts("Playing last track. Skip command ignored.");
		  }
                  break;
	       }
	       cd_play(CMD_SKIP);
	}
     | CMD_STATUS
        {
            /* 
             * Give the status of the CDROM
             */
	    initcdrom();
	    cd_status();
        }
     | CMD_DEVICE devstring
	{ 
	   /*
            * We are specifing a new device to open. Check if one is already open
            * and close it. Flag the change. If it is not already open, do nothing
            * other than update the device to open
            */
	   if (cdromok) {
	       close(f);
	       cdromok=FALSE;
	   }
           strcpy(cd_dev,$2);
	}
      ;
        
number: NUMBER {$$ = $1; }
      ;

devstring: DEVSTRING { $$ = $1; }
        ;
%%

void usage(char *arg0)
{
	int i;

	fputs("CDplayer 2.0 by Mark Buckaway (mark@datasoft.com)\n",stderr);
        fputs("(C) 1993,1994 DataSoft Communications\n",stderr);	  
#ifdef NEC_CDROM
	fprintf(stderr,"Compiled to open: %s (NEC) (%s)\n",DEV_CDROM,OS);
#else
	fprintf(stderr,"Compiled to open: %s (%s)\n",DEV_CDROM,OS);
#endif
	fprintf(stderr,"Usage: %s [device /dev/cdrom] command [track no/volume no]\n",arg0);
	fputs("Available commands:\nplay stop pause resume volume eject skip info status device\n",stderr);
	exit(1);
}

void initcdrom()
{
     FILE *fp;

     /*
      * Check if we already initialized the cdrom device and obtained
      * a file handle for the CDROM. If yes, updated the subchannel
      * info and return, if not, init the drive.
      */
     if (cdromok) {
        cd_subchnl();
        return;
     }
#ifndef COHERNET 
     /* 
      * Check if CDROM device is already mounted, and give an error if it is.
      * This routine checks the mtab for any iso9660 file system, and then compares
      * the name of the device for that file system to the device we want to you.
      * If a match is found, the device is assumed to be already mounted.
      *
      * This check is bypassed for Coherent, as they can't yet mount iso9660
      * filesystems.
      */
     if ((fp=setmntent(MOUNTED,"r"))==NULL) {
	  fprintf(stderr,"Couldn't open %s: %s\n",MOUNTED,strerror(errno));
	  exit(1);
     }
     while ((mnt=getmntent(fp))!=NULL) {
	  if (strcmp(mnt->mnt_type,"iso9660")==0) {
               if (strcmp(mnt->mnt_fsname,cd_dev)!=0) continue;
	       if (tkcd) {
		  puts("ERROR");
		 } else {
	          printf("CDROM \"%s\" appears to be already mounted. Unmount CDROM first.\n",cd_dev);
	       }
	       endmntent(fp);
	       exit(1);
	  }
     }
     endmntent(fp);
#endif

     /*
      * Open the CD device.
      */
     if ((f=open(cd_dev,O_RDONLY))==-1) {
	  if (tkcd) {
	     puts("ERROR");
	    } else {
	     perror("Error opening cdrom device");
	  }
	  exit(1);
     }

     /* 
      * Get table of content header entry for min and max tracks and 
      * store these for future reference
      */
     if ((ioctl(f,CDROMREADTOCHDR,&tochdr))==-1) {
	  if (tkcd) {
 	     puts("ERROR");
	   } else {
	     perror("ioctl(CDROMREADTOHDR)");
	  }
	  exit(1);
     }
     cd_subchnl();
     cdromok=TRUE;
}

/*
 * Play a Audio CD starting at track tochdr.chth_trk0 (global variable)
 */
void cd_play(int cmd)
{
     ti.cdti_ind0=1;
     ti.cdti_trk1=tochdr.cdth_trk1;
     ti.cdti_ind1=1;
     if ((ioctl(f,CDROMPLAYTRKIND,&ti))==-1) {
	 if (!tkcd) {
	     perror("ioctl(CDROMPLAYTRKIND)");
	    } else {
	     puts("PLAY;ERROR");
         } 
         exit(1);
     }
     if (!tkcd) {
       if (cmd==CMD_SKIP) {
          printf("Audio CD skipped to track %d.\n",ti.cdti_trk0);
        } else {
          printf("Audio CD playing. (Start track %d)\n",ti.cdti_trk0);
       }
      } else {
	 printf("PLAY;OK;%d\n",ti.cdti_trk0);
     }       
}

void cd_paused()
{
     if ((ioctl(f,CDROMPAUSE))==-1) {
	 if (!tkcd) {
            perror("ioctl(CDROMPAUSE)");
	   } else {
            puts("PAUSE;ERROR");
	 }
         exit(1);
     }
}

void cd_stop()
{
     if (subchnl.cdsc_audiostatus!=CDROM_AUDIO_PAUSED) {
        cd_paused();
     }
     if ((ioctl(f,CDROMSTOP))==-1) {
	if (tkcd) {
           perror("ioctl(CDROMSTOP)");
	  } else {
           puts("STOP;ERROR");
	}
        exit(1);
     }

}

void cd_subchnl()
{
     /*
      * Get the subchannel info (for CD status, etc) and store it for later.
      */
     subchnl.cdsc_format=CDROM_MSF;
     if ((ioctl(f,CDROMSUBCHNL,&subchnl))==-1) {
	 if (tkcd) {
	    puts("ERROR");
	   } else {
            perror("ioctl(CDROMSUBCHNL)");
	 }
         exit(1);
     }
}

void cd_status()
{
               /*
                * Print the status of the CDROM drive and the current track in play
                */
	       if (tkcd) {
		  printf("STATUS;");
		 } else {
	          printf("STATUS: ");
	       }
	       switch (subchnl.cdsc_audiostatus) {
	              case CDROM_AUDIO_INVALID:
	  		if (tkcd) {
		  	    puts("CANT");
		 	  } else {
	                    puts("Audio status not supported");
                        }
		        break;
                      case CDROM_AUDIO_PLAY:
			 if (!tkcd) {
                            printf("Audio CD playing. Current track: %d\n",subchnl.cdsc_trk);
			   } else {
			    cd_tocentry(CDROM_LEADOUT);
			    printf("PLAY;%d;%lu;%lu;%d;%lu\n",subchnl.cdsc_trk,
					((subchnl.cdsc_absaddr.msf.minute*C_TIME)+
					(subchnl.cdsc_absaddr.msf.second*75)+
					subchnl.cdsc_absaddr.msf.frame),
					((subchnl.cdsc_reladdr.msf.minute*C_TIME)+
					(subchnl.cdsc_reladdr.msf.second*75)+
					subchnl.cdsc_reladdr.msf.frame),
					tochdr.cdth_trk1,
                     			((tocentry.cdte_addr.msf.minute*C_TIME)+
                     			(tocentry.cdte_addr.msf.second*75)+
					tocentry.cdte_addr.msf.frame));
			 }							
                         break;
                      case CDROM_AUDIO_PAUSED:
			 if (!tkcd) {
                            printf("Audio CD paused. Current track: %d\n",subchnl.cdsc_trk);
			   } else {
			    cd_tocentry(CDROM_LEADOUT);
			    printf("PAUSED;%d;%lu;%lu;%d;%lu\n",subchnl.cdsc_trk,
					((subchnl.cdsc_absaddr.msf.minute*C_TIME)+
					(subchnl.cdsc_absaddr.msf.second*75)+
					subchnl.cdsc_absaddr.msf.frame),
					((subchnl.cdsc_reladdr.msf.minute*C_TIME)+
					(subchnl.cdsc_reladdr.msf.second*75)+
					subchnl.cdsc_reladdr.msf.frame),
					tochdr.cdth_trk1,
                     			((tocentry.cdte_addr.msf.minute*C_TIME)+
                     			(tocentry.cdte_addr.msf.second*75)+
					tocentry.cdte_addr.msf.frame));
			 } 							
                         break;
                      case CDROM_AUDIO_COMPLETED:
			 if (tkcd) {
			    puts("DONE");
			   } else {
                            puts("Audio CD completed");
			 }
                         break;
                      case CDROM_AUDIO_ERROR:
			 if (tkcd) {
			    puts("STATUS:ERROR");
			   } else {
                            puts("Audio CD stopped due to error");
			 }
                         break;
                      case CDROM_AUDIO_NO_STATUS:
			 if (tkcd) {
			    puts("NONE");
			   } else {
                            puts("No status to return: Drive stopped or open.");
			 }
               }
}

void cd_tocentry(int track)
{
    tocentry.cdte_track=track;
    tocentry.cdte_format=CDROM_MSF;
    if ((ioctl(f,CDROMREADTOCENTRY,&tocentry))==-1) {
	 if (tkcd) {
	    puts("ERROR");
	   } else {
	    perror("ioctl(CDROMREADTOCENTRY)");
	 }
	 exit(1);
    }
}

int main(int argc, char *argv[])
{
     int i,cur_trk;
     char *cd_env;

     /*
      * Init select global varibles
      */
     progname=*argv;
     cdromok=FALSE;
     tkcd=FALSE;
     /*
      * Check if a command was given. Unlike most UNIX commands, we tell the
      * user the available options if no command line options are given.
      */
     if (argc<2)
	usage(progname);
     /*
      * Setup global variables for command line parser
      */
      for (i=1;i<argc;i++) {
	  strcat(cmdline,argv[i]);
	  strcat(cmdline," ");
      }
      if ((cmdlen=strlen(cmdline))>MAXCMDLINE)
		cmdlen=MAXCMDLINE;
      cmd_done=FALSE; 	      
     /*
      * Check if CD_DEVICE apparent in the environment, if not use
      * the built in default for the device to open.
      */
     if ((cd_env=getenv("CD_DEVICE"))==NULL) 
        strcpy(cd_dev,DEV_CDROM);
       else
        strcpy(cd_dev,cd_env);
     yyparse();
     close(f);
     return(0);
}

int yyerror(char *msg)
{
	usage(progname);
	fprintf(stderr,"ERROR: Bad command line: %s\n",msg);
}

int yywrap()
{
	return(1);
}
