1 /* WMix 3.0 -- a mixer using the OSS mixer API.
2  * Copyright (C) 2000, 2001
3  *	Daniel Richard G. <skunk@mit.edu>,
4  *	timecop <timecop@japan.co.jp>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 #include "config.h"
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <math.h>
30 
31 #include <X11/X.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xutil.h>
34 #include <X11/Xatom.h>
35 #include <X11/extensions/shape.h>
36 #include <X11/xpm.h>
37 #include <X11/cursorfont.h>
38 
39 #include "include/master.xpm"
40 #include "include/led-on.xpm"
41 #include "include/led-off.xpm"
42 
43 #include "include/common.h"
44 #include "include/misc.h"
45 #include "include/mixer.h"
46 #include "include/ui_x.h"
47 
48 #ifndef PI
49 #define PI M_PI
50 #endif
51 
52 #define LED_POS_RADIUS 8
53 #define KNOB_CENTER_X 49
54 #define KNOB_CENTER_Y 48
55 #define LED_WIDTH 6
56 #define LED_HEIGHT 6
57 
58 typedef struct _Dockapp Dockapp;
59 
60 struct _Dockapp {
61     int width;
62     int height;
63     Pixmap pixmap;
64     Pixmap mask;
65     GC gc;
66     int ctlength;
67 
68     Window osd;
69     GC osd_gc;
70     int osd_width;
71     bool osd_mapped;
72 
73 };
74 
75 extern Config config;
76 
77 static Pixmap led_on_pixmap;
78 static Pixmap led_on_mask;
79 static Pixmap led_off_pixmap;
80 static Pixmap led_off_mask;
81 
82 #define copy_xpm_area(x, y, w, h, dx, dy) \
83     XCopyArea(display, dockapp.pixmap, dockapp.pixmap, dockapp.gc, \
84 	    x, y, w, h, dx, dy)
85 
86 /* local prototypes */
87 static Cursor create_null_cursor(Display *x_display);
88 
89 /* ui stuff */
90 static void draw_stereo_led(void);
91 static void draw_rec_led(void);
92 static void draw_mute_led(void);
93 static void draw_percent(void);
94 static void draw_knob(float volume);
95 static void draw_slider(float offset);
96 
97 /* global variables */
98 static Dockapp dockapp;
99 static Display *display;
100 static Window win;
101 static Window iconwin;
102 static Cursor hand_cursor;
103 static Cursor null_cursor;
104 static Cursor norm_cursor;
105 static Cursor bar_cursor;
106 
107 /* public methods */
dockapp_init(Display * x_display)108 void dockapp_init(Display *x_display)
109 {
110     display = x_display;
111 }
112 
redraw_window(void)113 void redraw_window(void)
114 {
115     XCopyArea(display, dockapp.pixmap, iconwin, dockapp.gc,
116 	      0, 0, dockapp.width, dockapp.height, 0, 0);
117     XCopyArea(display, dockapp.pixmap, win, dockapp.gc,
118 	      0, 0, dockapp.width, dockapp.height, 0, 0);
119 }
120 
ui_update(void)121 void ui_update(void)
122 {
123     draw_stereo_led();
124     draw_rec_led();
125     draw_mute_led();
126     draw_knob(mixer_get_volume());
127     draw_slider(mixer_get_balance());
128     redraw_window();
129 }
130 
knob_turn(float delta)131 void knob_turn(float delta)
132 {
133     mixer_set_volume_rel(delta);
134     draw_knob(mixer_get_volume());
135     redraw_window();
136 }
137 
slider_move(float delta)138 void slider_move(float delta)
139 {
140     mixer_set_balance_rel(delta);
141     draw_slider(mixer_get_balance());
142     redraw_window();
143 }
144 
blit_string(const char * text)145 int blit_string(const char *text)
146 {
147     register int i;
148     register int c;
149     register int k;
150 
151     k = 0;
152     copy_xpm_area(0, 87, 256, 9, 0, 96);
153 
154     for (i = 0; text[i] || i > 31; i++) {
155 	c = toupper(text[i]);
156 	if (c == '-') {
157 	    copy_xpm_area(60, 67, 6, 8, k, 96);
158 	    k += 6;
159 	}
160 	if (c == ' ') {
161 	    copy_xpm_area(66, 67, 6, 8, k, 96);
162 	    k += 6;
163 	}
164 	if (c == '.') {
165 	    copy_xpm_area(72, 67, 6, 8, k, 96);
166 	    k += 6;
167 	}
168 	if (c >= 'A' && c <= 'Z') {	/* letter */
169 	    c = c - 'A';
170 	    copy_xpm_area(c * 6, 77, 6, 8, k, 96);
171 	    k += 6;
172 	} else if (c >= '0' && c <= '9') {	/* number */
173 	    c = c - '0';
174 	    copy_xpm_area(c * 6, 67, 6, 8, k, 96);
175 	    k += 6;
176 	}
177     }
178     dockapp.ctlength = k;
179     return k;
180 }
181 
scroll_text(int x,int y,int width,bool reset)182 void scroll_text(int x, int y, int width, bool reset)
183 {
184     static int pos;
185     static int first;
186     static int stop;
187 
188     /* no text scrolling at all */
189     if (!config.scrolltext) {
190 	if (!reset)
191 	    return;
192 	copy_xpm_area(0, 96, 58, 9, x, y);
193 	redraw_window();
194 	return;
195     }
196 
197     if (reset) {
198 	pos = 0;
199 	first = 0;
200 	stop = 0;
201 	copy_xpm_area(0, 87, width, 9, x, y);
202     }
203 
204     if (stop) {
205 	return;
206     }
207 
208     if ((first == 0) && pos == 0) {
209 	pos = width;
210 	first = 1;
211     }
212 
213     if (pos < -(dockapp.ctlength)) {
214 	first = 1;
215 	pos = width;
216 	stop = 1;
217 	return;
218     }
219     pos -= 2;
220 
221     if (pos > 0) {
222 	copy_xpm_area(0, 87, pos, 9, x, y);	/* clear */
223 	copy_xpm_area(0, 96, width - pos, 9, x + pos, y);
224     } else {			/* don't need to clear, already in text */
225 	copy_xpm_area(abs(pos), 96, width, 9, x, y);
226     }
227     redraw_window();
228     return;
229 }
230 
new_window(char * name,int width,int height)231 void new_window(char *name, int width, int height)
232 {
233     XpmAttributes attr;
234     Pixel fg, bg;
235     XGCValues gcval;
236     XSizeHints sizehints;
237     XClassHint classhint;
238     XWMHints wmhints;
239     XTextProperty wname;
240 
241     dockapp.width = width;
242     dockapp.height = height;
243 
244     sizehints.flags = USSize | USPosition;
245     sizehints.x = 0;
246     sizehints.y = 0;
247     sizehints.width = width;
248     sizehints.height = height;
249 
250     fg = BlackPixel(display, DefaultScreen(display));
251     bg = WhitePixel(display, DefaultScreen(display));
252 
253     win = XCreateSimpleWindow(display, DefaultRootWindow(display),
254 			      sizehints.x, sizehints.y,
255 			      sizehints.width, sizehints.height, 1, fg,
256 			      bg);
257 
258     iconwin = XCreateSimpleWindow(display, win, sizehints.x, sizehints.y,
259 				  sizehints.width, sizehints.height, 1, fg,
260 				  bg);
261 
262     XSetWMNormalHints(display, win, &sizehints);
263     classhint.res_name = name;
264     classhint.res_class = name;
265     XSetClassHint(display, win, &classhint);
266 
267 #define INPUT_MASK \
268     ButtonPressMask \
269     | ExposureMask \
270     | ButtonReleaseMask \
271     | PointerMotionMask \
272     | LeaveWindowMask \
273     | StructureNotifyMask
274 
275     XSelectInput(display, win, INPUT_MASK);
276     XSelectInput(display, iconwin, INPUT_MASK);
277 
278 #undef INPUT_MASk
279 
280     XStringListToTextProperty(&name, 1, &wname);
281     XSetWMName(display, win, &wname);
282 
283     gcval.foreground = fg;
284     gcval.background = bg;
285     gcval.graphics_exposures = 0;
286 
287     dockapp.gc =
288 	XCreateGC(display, win,
289 		  GCForeground | GCBackground | GCGraphicsExposures,
290 		  &gcval);
291 
292     attr.exactColors = 0;
293     attr.alloc_close_colors = 1;
294     attr.closeness = 30000;
295     attr.valuemask = (XpmExactColors | XpmAllocCloseColors | XpmCloseness);
296     if ((XpmCreatePixmapFromData(display, DefaultRootWindow(display),
297 				master_xpm, &dockapp.pixmap, &dockapp.mask,
298 				&attr) != XpmSuccess) ||
299 	    (XpmCreatePixmapFromData(display, DefaultRootWindow(display),
300 				led_on_xpm, &led_on_pixmap, &led_on_mask,
301 				&attr) != XpmSuccess) ||
302 	    (XpmCreatePixmapFromData(display, DefaultRootWindow(display),
303 				led_off_xpm, &led_off_pixmap, &led_off_mask,
304 				&attr) != XpmSuccess)) {
305 	fputs("Cannot allocate colors for the dockapp pixmaps!\n", stderr);
306 	exit(EXIT_FAILURE);
307     }
308 
309     XShapeCombineMask(display, win, ShapeBounding, 0, 0, dockapp.mask,
310 		      ShapeSet);
311     XShapeCombineMask(display, iconwin, ShapeBounding, 0, 0, dockapp.mask,
312 		      ShapeSet);
313 
314     wmhints.initial_state = WithdrawnState;
315     wmhints.icon_window = iconwin;
316     wmhints.icon_x = sizehints.x;
317     wmhints.icon_y = sizehints.y;
318     wmhints.window_group = win;
319     wmhints.flags =
320 	StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
321     XSetWMHints(display, win, &wmhints);
322 
323     hand_cursor = XCreateFontCursor(display, XC_hand2);
324     norm_cursor = XCreateFontCursor(display, XC_left_ptr);
325     bar_cursor = XCreateFontCursor(display, XC_sb_up_arrow);
326     null_cursor = create_null_cursor(display);
327 
328     XMapWindow(display, win);
329 }
330 
new_osd(int width,int height)331 void new_osd(int width, int height)
332 {
333     Window osd;
334     Pixel fg, bg;
335     XGCValues gcval;
336     GC gc;
337     XSizeHints sizehints;
338     XSetWindowAttributes xattributes;
339     int win_layer = 6;
340     XFontStruct *fs = NULL;
341 
342     sizehints.flags = USSize | USPosition;
343     sizehints.x = (DisplayWidth(display, 0) - width) / 2;
344     sizehints.y = (DisplayHeight(display, 0) - 120);
345     sizehints.width = width;
346     sizehints.height = height;
347     xattributes.save_under = True;
348     xattributes.override_redirect = True;
349     xattributes.cursor = None;
350 
351 
352     fg = WhitePixel(display, DefaultScreen(display));
353     bg = BlackPixel(display, DefaultScreen(display));
354 
355     osd = XCreateSimpleWindow(display, DefaultRootWindow(display),
356 			      sizehints.x, sizehints.y, width, height,
357 			      0, fg, bg);
358 
359     XSetWMNormalHints(display, osd, &sizehints);
360     XChangeWindowAttributes(display, osd, CWSaveUnder | CWOverrideRedirect,
361 			    &xattributes);
362     XStoreName(display, osd, "osd");
363     XSelectInput(display, osd, ExposureMask);
364 
365     XChangeProperty(display, osd, XInternAtom(display, "_WIN_LAYER", False),
366 	    XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&win_layer, 1);
367 
368     gcval.foreground = get_color(display, config.osd_color);
369     gcval.background = bg;
370     gcval.graphics_exposures = 0;
371 
372     /*
373      * -sony-fixed-medium-r-normal--24-170-100-100-c-120-iso8859-1
374      * -misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-* */
375 
376     /* try our cool scaled 36pt fixed font */
377     fs = XLoadQueryFont(display,
378 	    "-misc-fixed-medium-r-normal--36-*-75-75-c-*-iso8859-*");
379 
380     if (fs == NULL) {
381 	/* they don't have it! */
382 	/* try our next preferred font (100dpi sony) */
383 	fprintf(stderr, "Trying alternate font\n");
384 	fs = XLoadQueryFont(display,
385 		"-sony-fixed-medium-r-normal--24-*-100-100-c-*-iso8859-*");
386 
387 	/* they don't have the sony font either */
388 	if (fs == NULL) {
389 	    fprintf(stderr, "Trying \"fixed\" font\n");
390 	    fs = XLoadQueryFont(display,
391 		    "fixed");
392 	    /* if they don't have the fixed font, we've got different kind
393 	     * of problems */
394 	    if (fs == NULL) {
395 		fprintf(stderr, "Your X server is probably broken\n");
396 		exit(1);
397 	    }
398 	}
399     }
400 
401     gc =
402 	XCreateGC(display, osd,
403 		  GCForeground | GCBackground | GCGraphicsExposures,
404 		  &gcval);
405     XSetFont(display, gc, fs->fid);
406 
407     dockapp.osd = osd;
408     dockapp.osd_gc = gc;
409     dockapp.osd_width = width;
410     dockapp.osd_mapped = false;
411 }
412 
update_osd(float volume,bool up)413 void update_osd(float volume, bool up)
414 {
415     int i;
416     int foo;
417     static int bar;
418 
419     if (config.osd) {
420 	foo =
421 	    (((dockapp.osd_width / 100) * (volume * 100)) / 20) + 1;
422 
423 	if ((foo != bar) || up) {
424 	    XClearArea(display, dockapp.osd, ((bar - 1) * 20), 30,
425 		       (foo * 20), 25, 1);
426 	    for (i = 1; i < foo; i++)
427 		XFillRectangle(display, dockapp.osd, dockapp.osd_gc,
428 			       i * 20, 30, 5, 25);
429 	}
430 	bar = foo;
431     }
432 }
433 
unmap_osd(void)434 void unmap_osd(void)
435 {
436     if (config.osd) {
437 	XUnmapWindow(display, dockapp.osd);
438 	XFlush(display);
439 	dockapp.osd_mapped = false;
440     }
441 }
442 
map_osd(void)443 void map_osd(void)
444 {
445     if (config.osd) {
446 	XMapRaised(display, dockapp.osd);
447 	XDrawString(display, dockapp.osd, dockapp.osd_gc, 1, 25,
448 		mixer_get_channel_name(), strlen(mixer_get_channel_name()));
449 	update_osd(mixer_get_volume(), true);
450 	XFlush(display);
451 	dockapp.osd_mapped = true;
452     }
453 }
454 
osd_mapped(void)455 bool osd_mapped(void)
456 {
457     return dockapp.osd_mapped;
458 }
459 
set_cursor(int type)460 void set_cursor(int type)
461 {
462     static int oldtype;
463 
464     if (oldtype == type)
465 	return;
466 
467     switch (type) {
468 	case NULL_CURSOR:
469 	    XDefineCursor(display, win, null_cursor);
470 	    XDefineCursor(display, iconwin, null_cursor);
471 	    break;
472 	case NORMAL_CURSOR:
473 	    XDefineCursor(display, win, norm_cursor);
474 	    XDefineCursor(display, iconwin, norm_cursor);
475 	    break;
476 	case HAND_CURSOR:
477 	    XDefineCursor(display, win, hand_cursor);
478 	    XDefineCursor(display, iconwin, hand_cursor);
479 	    break;
480 	case BAR_CURSOR:
481 	    XDefineCursor(display, win, bar_cursor);
482 	    XDefineCursor(display, iconwin, bar_cursor);
483 	    break;
484     }
485     oldtype = type;
486 }
487 
488 /* private */
draw_stereo_led(void)489 static void draw_stereo_led(void)
490 {
491     if (mixer_is_stereo())	/* stereo capable */
492 	copy_xpm_area(78, 0, 9, 7, 28, 14);	/* light up LCD */
493     else			/* mono channel */
494 	copy_xpm_area(78, 7, 9, 7, 28, 14);	/* turn off LCD */
495 }
496 
draw_rec_led(void)497 static void draw_rec_led(void)
498 {
499     if (mixer_is_rec())		/* record enabled */
500 	copy_xpm_area(65, 0, 13, 7, 4, 14);	/* Light up LCD */
501     else			/* record disabled */
502 	copy_xpm_area(65, 7, 13, 7, 4, 14);	/* turn off LCD */
503 }
504 
draw_mute_led(void)505 static void draw_mute_led(void)
506 {
507     if (mixer_is_muted())	/* mute */
508 	copy_xpm_area(65, 14, 20, 7, 39, 14);	/* light up LCD */
509     else			/* unmute */
510 	copy_xpm_area(65, 21, 20, 7, 39, 14);	/* turn off LCD */
511 }
512 
draw_percent(void)513 static void draw_percent(void)
514 {
515     int volume = (int)(mixer_get_volume() * 100);
516 
517     copy_xpm_area(0, 87, 18, 9, 41, 22);	/* clear percentage */
518 
519     if (volume < 100) {
520 	if (volume >= 10)
521 	    copy_xpm_area((volume / 10) * 6, 67, 6, 9, 47, 22);
522 	copy_xpm_area((volume % 10) * 6, 67, 6, 9, 53, 22);
523     } else {
524 	copy_xpm_area(6, 67, 6, 9, 41, 22);
525 	copy_xpm_area(0, 67, 6, 9, 47, 22);
526 	copy_xpm_area(0, 67, 6, 9, 53, 22);
527     }
528 }
529 
draw_knob(float volume)530 static void draw_knob(float volume)
531 {
532     float bearing, led_x, led_y;
533     int led_topleft_x, led_topleft_y;
534     Pixmap led_pixmap, led_mask;
535 
536     bearing = (1.25 * PI) - (1.5 * PI) * volume;
537 
538     led_x = KNOB_CENTER_X + LED_POS_RADIUS * cos(bearing);
539     led_y = KNOB_CENTER_Y - LED_POS_RADIUS * sin(bearing);
540 
541     led_topleft_x = (int)(led_x - (LED_WIDTH / 2.0) + 0.5);
542     led_topleft_y = (int)(led_y - (LED_HEIGHT / 2.0) + 0.5);
543 
544     /* clear previous knob picture */
545     copy_xpm_area(87, 0, 26, 26, 36, 35);
546 
547     if (mixer_is_muted()) {
548 	led_pixmap = led_off_pixmap;
549 	led_mask = led_off_mask;
550     } else {
551 	led_pixmap = led_on_pixmap;
552 	led_mask = led_on_mask;
553     }
554     XCopyArea(display, led_pixmap, dockapp.pixmap, dockapp.gc,
555 	    0, 0, LED_WIDTH, LED_HEIGHT, led_topleft_x, led_topleft_y);
556     draw_percent();
557 }
558 
draw_slider(float offset)559 static void draw_slider(float offset)
560 {
561     int x = (offset * 50) / 5;
562 
563     copy_xpm_area(65, 45, 27, 20, 4, 40);	/* repair region. move */
564     copy_xpm_area(65, 29, 7, 15, 14 + x, 43);	/* slider */
565 }
566 
create_null_cursor(Display * x_display)567 static Cursor create_null_cursor(Display *x_display)
568 {
569     Pixmap cursor_mask;
570     XGCValues gcval;
571     GC gc;
572     XColor dummy_color;
573     Cursor cursor;
574 
575     cursor_mask = XCreatePixmap(x_display, DefaultRootWindow(x_display), 1, 1, 1);
576     gcval.function = GXclear;
577     gc = XCreateGC(x_display, cursor_mask, GCFunction, &gcval);
578     XFillRectangle(x_display, cursor_mask, gc, 0, 0, 1, 1);
579     dummy_color.pixel = 0;
580     dummy_color.red = 0;
581     dummy_color.flags = 04;
582     cursor = XCreatePixmapCursor(x_display, cursor_mask, cursor_mask,
583 		&dummy_color, &dummy_color, 0, 0);
584     XFreePixmap(x_display, cursor_mask);
585     XFreeGC(x_display, gc);
586 
587     return cursor;
588 }
589 
get_color(Display * display,char * color_name)590 unsigned long get_color(Display *display, char *color_name)
591 {
592     XColor color;
593     XWindowAttributes winattr;
594 
595     XGetWindowAttributes(display,
596 	    RootWindow(display, DefaultScreen(display)), &winattr);
597 
598     color.pixel = 0;
599     XParseColor(display, winattr.colormap, color_name, &color);
600 
601     color.flags = DoRed | DoGreen | DoBlue;
602     XAllocColor(display, winattr.colormap, &color);
603 
604     return color.pixel;
605 }
606