1 //  Copyright (C) 1998 by Jean-Marc Zucconi (jmz@FreeBSD.ORG)
2 //  Everyone is granted permission to copy, modify and redistribute.
3 //  This notice must be preserved on all copies or derivates.
4 
5 #include <string.h>
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <fcntl.h>
9 #include <err.h>
10 #include <errno.h>
11 #include <stdio.h>
12 #include <sys/file.h>
13 #include <sys/cdio.h>
14 #include <math.h>
15 #include <sys/ioctl.h>
16 #include <stdarg.h>
17 
18 #define DEVICE "cd0"
19 
20 void open_cd (), setvol (int), do_update (), read_toc (), play_msf (int, int, int),
21     time_track (int, int, int), play_track (int), popup (int, int),
22     out (char *, ...);
23 int get_info ();
24 
25 class button {
26 private:
27     int state;
28     char name[20];
29 public:
button(char * s)30     button (char *s) {state=0; strcpy (name, s);}
~button()31     ~button () {}
set(int i)32     void set (int i) {
33 	if (state != i) {
34 	    state = i;
35 	    if (state == 0)
36 		out (".%s configure -fg $fg", name);
37 	    else
38 		out (".%s configure -fg $active", name);
39 	}
40     }
is_set()41     int is_set () {return state;}
42 };
43 
44 
45 int cd_fd = -1;
46 char *device;
47 int current_track = 0, current_min = 0, current_sec = 0, current_frame = 0;
48 int last_in_toc = 0;
49 int time_btn = 0, track_btn = 0;
50 
51 int linear = 0;
52 
53 int animate = 1;
54 int v_animate = 0;
55 
56 struct cd_toc_entry toc_buffer[100];
57 
58 typedef enum {
59     PLAY, STOP, PAUSE, EJECT, PREV, NEXT, REWIND, FF,
60     TRACK, TIME, VOLUME, UPDATE, QUIT, NONE
61 } command;
62 
63 #define C(x) {#x, sizeof(#x)-1, x}
64 struct {
65     char *s;
66     int l;
67     command cmd;
68 } clist[] = {
69     C(PLAY),
70     C(STOP),
71     C(PAUSE),
72     C(EJECT),
73     C(PREV),
74     C(NEXT),
75     C(REWIND),
76     C(FF),
77     C(TRACK),
78     C(TIME),
79     C(VOLUME),
80     C(UPDATE),
81     C(QUIT),
82     {0, 0, NONE}
83 };
84 
85 button play_button ("play"),  pause_button ("pause"), stop_button ("stop"),
86     eject_button ("eject"), prev_button ("prev"),  next_button ("next"),
87     rewind_button ("rewind"),  ff_button ("ff");
88 
89 #define M(t) toc_buffer[t].addr.msf.minute
90 #define S(t) toc_buffer[t].addr.msf.second
91 #define F(t) toc_buffer[t].addr.msf.frame
92 
main(int argc,char ** argv)93 int main (int argc, char **argv)
94 {
95     char dev[20], line[50];
96     int i;
97     char *a;
98 
99     if (argv[1] && !strcmp (argv[1], "-l")) {
100 	linear = 1;
101 	a = argv[2];
102     } else
103 	a = argv[1];
104     if (!a)
105 	a = DEVICE;
106 
107     device = getenv ("CDPLAYER");
108     if (!device)
109 	device = a;
110 
111     a = strchr (device, '/');
112     if (!a && strlen (device) < 10) {
113 	sprintf (dev, "/dev/%s", device);
114 	device = dev;
115     }
116     while (fgets (line, 40, stdin)) {
117 	for (i = 0; clist[i].s; i++)
118 	    if (strncasecmp (clist[i].s, line, clist[i].l) == 0)
119 		break;
120 	i = clist[i].cmd;
121 	if ((cd_fd == -1) && (i != UPDATE) && (i != QUIT))
122 	    i = NONE;
123 	switch (i) {
124 	case UPDATE:
125 	    do_update ();
126 	    break;
127 	case PLAY:
128 	    if (play_button.is_set ())
129 		break;
130 	    play_track (1);
131 	    play_button.set (1);
132 	    break;
133 	case STOP:
134 	    if (play_button.is_set ()) {
135 		play_button.set (0);
136 		pause_button.set (0);
137 		ioctl (cd_fd, CDIOCSTOP);
138 		stop_button.set (1);
139 		time_track (-1, -1, -1);
140 	    }
141 	    break;
142 	case PAUSE:
143 	    if (play_button.is_set ())
144 		if (!pause_button.is_set () && !ioctl (cd_fd, CDIOCPAUSE))
145 		    pause_button.set (1);
146 		else if (!ioctl (cd_fd, CDIOCRESUME))
147 		    pause_button.set (0);
148 	    break;
149 	case EJECT:
150 	    if (!eject_button.is_set ()) {
151 		i = ioctl (cd_fd, CDIOCALLOW);
152 		if (!i)
153 		    i = ioctl (cd_fd, CDIOCEJECT);
154 		if (!i) {
155 		    eject_button.set (1);
156 		    play_button.set (0);
157 		    pause_button.set (0);
158 		    stop_button.set (0);
159 		    close (cd_fd);
160 		    cd_fd = -1;
161 		    last_in_toc = 0;
162 		    animate = 1;
163 		    v_animate = 0;
164 		}
165 	    }
166 	    break;
167 	case PREV:
168 	    if (!play_button.is_set ())
169 		break;
170 	    get_info ();
171 	    if (current_sec > 2)
172 		play_track (current_track);
173 	    else
174 		play_track (current_track-1);
175 	    break;
176 	case NEXT:
177 	    if (!play_button.is_set ())
178 		break;
179 	    play_track (current_track + 1);
180 	    break;
181 	case REWIND:
182 	    if (atoi (line+clist[i].l) == 1)
183 		rewind_button.set (1);
184 	    else
185 		rewind_button.set (0);
186 	    break;
187 	case FF:
188 	    if (atoi (line+clist[i].l) == 1)
189 		ff_button.set (1);
190 	    else
191 		ff_button.set (0);
192 	    break;
193 	case TRACK:
194 	    i = atoi (line+clist[i].l);
195 	    if (i == 2) {
196 		char *p = strchr (line, '.') + 1;
197 		i = atoi (p);
198 		p = strchr (p, '.') + 1;
199 		popup (i, atoi(p));
200 	    } else if (i < 0)
201 		play_track (-i);
202 	    else
203 		track_btn = i;
204 	    break;
205 	case TIME:
206 	    time_btn = atoi (line+clist[i].l);
207 	    break;
208 	case VOLUME:
209 	    setvol (atoi (line+clist[i].l));
210 	    break;
211 	case QUIT:
212 	    printf ("exit\n");
213 	    fflush (stdout);
214 	    sleep (3);
215 	    exit (0);
216 	default:
217 	    break;
218 	}
219 	printf ("after %d do_update\n",
220 		play_button.is_set () && !pause_button.is_set () ? 100 : 200);
221 	fflush (stdout);
222     }
223     exit (0);
224 }
225 
226 void
open_cd()227 open_cd ()
228 {
229     cd_fd = open (device, O_RDONLY); /* warning: this may take a long time */
230     if (cd_fd < 0) {
231 	if (errno == ENXIO)
232 	    return; /* open says 'Device not configured' if there is no cd in */
233 	if (errno == EBUSY || EINVAL)
234 	    return; /* loading the CD? */
235 {FILE *f=fopen("/tmp/cdplayer.error", "w"); fprintf(f, "exiting with errno=%d\n", errno);}
236 	err (1, device);
237     }
238 }
239 void
setvol(int l)240 setvol (int l)
241 {
242     struct ioc_vol v;
243 
244     l = linear ? int(l*2.55) : int(55.253*log(l+1.) +.01);
245     v.vol[0] = l;
246     v.vol[1] = l;
247     v.vol[2] = 0;
248     v.vol[3] = 0;
249     ioctl (cd_fd, CDIOCSETVOL, &v);
250 }
251 int
get_info()252 get_info ()
253 {
254     struct ioc_read_subchannel s;
255     struct cd_sub_channel_info data;
256     bzero(&s, sizeof(s));
257     s.data = &data;
258     s.data_len = sizeof (data);
259     s.address_format = CD_MSF_FORMAT;
260     s.data_format = CD_CURRENT_POSITION;
261     ioctl (cd_fd, CDIOCREADSUBCHANNEL, (char *)&s);
262     current_min = data.what.position.reladdr.msf.minute;
263     current_sec = data.what.position.reladdr.msf.second;
264     current_frame = data.what.position.reladdr.msf.frame;
265     current_track = data.what.position.track_number;
266     return data.header.audio_status;
267 }
268 void
do_update()269 do_update ()
270 {
271     struct ioc_vol v;
272     int i, vol;
273 
274     if (cd_fd < 0) {
275 	open_cd ();
276 	if (cd_fd < 0) {
277 	    eject_button.set (1);
278 	    time_track (0, 0, 0);
279 	    if (animate < 0) {
280 		v_animate -= 10;
281 		out ("set volume %d", v_animate);
282 		if (v_animate == 0)
283 		    animate = 1;
284 	    } else {
285 		v_animate += 10;
286 		out ("set volume %d", v_animate);
287 		if (v_animate == 100)
288 		    animate = -1;
289 	    }
290 	    return;
291 	}
292 	eject_button.set (0);
293 	read_toc ();
294 	if (get_info () == 18)
295 	    time_track (current_min, current_sec, current_track);
296 	else
297 	    time_track (-1, -1, -1);
298 	ioctl (cd_fd, CDIOCGETVOL, &v);
299 	vol = (v.vol[0]+v.vol[1])/2;
300 	vol = linear ? int(vol/2.55) : int (exp (vol/55.253) - 1);
301 	out ("set volume %d", vol);
302     }
303     if (play_button.is_set () && !pause_button.is_set () &&
304 	(rewind_button.is_set () || ff_button.is_set ())) {
305 	int f;
306 	f = (M(current_track-1) + current_min)*60*75
307 	    + (S(current_track-1) + current_sec)*75
308 	    + F(current_track-1) + current_frame
309 	    + (ff_button.is_set () ? 10*75 : -10*75);
310 	if ((f > M(0)*60*75 + S(0)*75 + F(0)) &&
311 	    (f < M(last_in_toc)*60*75 + S(last_in_toc)*75 + F(last_in_toc))) {
312 	    int m = f/(60*75);
313 	    int s = (f - m*60*75)/75;
314 	    f -=  m*60*75 + s*75;
315 	    play_msf (m, s, f);
316 	}
317     }
318     i = get_info ();
319     switch (i) {
320     case 17:
321 	stop_button.set (0);
322 	pause_button.set (0);
323 	play_button.set (1);
324 	if (track_btn)
325 	    time_track (-1, -1, -1);
326 	else if (time_btn) {
327 	    int i = (M(current_track) - M(current_track-1)) * 75*60 +
328 	    (S(current_track) - S(current_track-1)) *75 +
329 	    F(current_track) - F(current_track-1);
330 
331 	    time_track (i/(60*75), (i-60*75*int(i/(60*75)))/75,	current_track);
332 	} else
333 	    time_track (current_min, current_sec, current_track);
334 	break;
335     case 18:
336 	stop_button.set (0);
337 	play_button.set (1);
338 	pause_button.set (1);
339 	break;
340     case 19:
341 	play_button.set (0);
342 	stop_button.set (1);
343 	time_track (-1, -1, -1);
344 	break;
345     case 20:
346     case 21:
347 	play_button.set (0);
348 	pause_button.set (0);
349 	stop_button.set (1);
350 	break;
351     }
352 }
353 void
read_toc()354 read_toc ()
355 {
356     struct ioc_toc_header h;
357     struct ioc_read_toc_entry t;
358     int n;
359 
360     n = ioctl (cd_fd, CDIOREADTOCHEADER, (char *)&h);
361     n =  h.ending_track - h.starting_track + 1;
362     t.address_format = CD_MSF_FORMAT;
363     t.starting_track = 1;
364     t.data_len = (n+1)*sizeof(struct cd_toc_entry);
365     t.data = toc_buffer;
366     ioctl (cd_fd, CDIOREADTOCENTRYS, (char *)&t);
367     toc_buffer[n].track = 255;
368     last_in_toc = n;
369 }
370 void
time_track(int m,int s,int t)371 time_track (int m, int s, int t)
372 {
373     if (m != -1)
374 	out ("set track %02d; set time %02d:%02d", t, m, s);
375     else
376 	time_track (M(last_in_toc), S(last_in_toc), last_in_toc);
377 }
378 void
play_track(int start)379 play_track (int start)
380 {
381 #if 0
382     struct ioc_play_track t;
383 
384     if (start == 0 || start > last_in_toc)
385 	return;
386     t.start_track = start;
387     t.start_index = 1;
388     t.end_track = last_in_toc;
389     t.end_index = last_in_toc;
390     ioctl (cd_fd, CDIOCPLAYTRACKS, &t);
391 #else
392     start--;
393     play_msf (M(start), S(start), F(start));
394 #endif
395 }
396 void
play_msf(int start_m,int start_s,int start_f)397 play_msf (int start_m, int start_s, int start_f)
398 {
399     struct ioc_play_msf a;
400     int i;
401 
402     a.start_m = start_m;
403     a.start_s = start_s;
404     a.start_f = start_f;
405     i = M(last_in_toc)*60*75 + S(last_in_toc)*75 + F(last_in_toc) - 1;
406     a.end_m = i/(60*75);
407     a.end_s = (i - a.end_m*60*75)/75;
408     a.end_f = i - a.end_s*75 - a.end_m*60*75;
409     i=ioctl (cd_fd, CDIOCPLAYMSF, (char *)&a);
410 }
411 void
out(char * fmt,...)412 out (char *fmt, ...)
413 {
414     va_list ap;
415 
416     va_start (ap, fmt);
417     vfprintf (stdout, fmt, ap);
418     va_end (ap);
419     putchar (';');
420 }
421 void
popup(int x,int y)422 popup (int x, int y)
423 {
424     int i, m, s, d;
425 
426     if (!last_in_toc)
427 	return;
428     out ("if {[winfo exists .pop]} {destroy .pop}; menu .pop");
429     for (i = 0; i < last_in_toc; i++) {
430 	d = (M(i+1)-M(i))*60*75 + (S(i+1)-S(i))*75 + F(i+1)-F(i);
431 	m = d/(60*75);
432 	s = (d - m*60*75)/75;
433 	out (".pop add command -label {%2d    %02d:%02d} -command \"cmd track %d\"", i+1, m, s, -i-1);
434     }
435     out ("tk_popup .pop %d %d", x, y);
436 }
437 
438