1 /*
2 * asmix is the AfterStep sound volume control knob for X Windows.
3 * Copyright (c) 1998 original author unknown
4 * Copyright (c) 1998-2004 Albert "Tigr" Dorofeev <albert@tigr.net>
5 * Copyright (c) 2000 John "wizgrav" Gravezas <wizgrav@netsmart.gr>
6 *
7 * This software is distributed under GPL. For details see LICENSE file.
8 */
9
10 #include "config.h"
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include <X11/Xlib.h>
17 #include <X11/xpm.h>
18 #include <X11/extensions/shape.h>
19 #include <time.h>
20 #include <math.h>
21 #include <X11/Xatom.h>
22 #include <sys/errno.h>
23 #include <sys/ioctl.h>
24 #include <fcntl.h>
25 #ifdef HAVE_SYS_SOUNDCARD_H
26 # include <sys/soundcard.h>
27 #endif
28 #ifdef HAVE_LINUX_SOUNDCARD_H
29 # include <linux/soundcard.h>
30 #endif
31
32 typedef struct mixer_channel_ident_t
33 {
34 char * channel_name;
35 int channel_id;
36 } mixer_channel_ident;
37
38 mixer_channel_ident channel_idents[] =
39 {
40 { "volume", SOUND_MIXER_VOLUME },
41 { "bass", SOUND_MIXER_BASS },
42 { "treble", SOUND_MIXER_TREBLE },
43 { "synth", SOUND_MIXER_SYNTH },
44 { "pcm", SOUND_MIXER_PCM },
45 { "speaker", SOUND_MIXER_SPEAKER },
46 { "line", SOUND_MIXER_LINE },
47 { "mic", SOUND_MIXER_MIC },
48 { "cd", SOUND_MIXER_CD },
49 { "imix", SOUND_MIXER_IMIX },
50 { "pcm2", SOUND_MIXER_ALTPCM },
51 { "record", SOUND_MIXER_RECLEV },
52 { "igain", SOUND_MIXER_IGAIN },
53 { "ogain", SOUND_MIXER_OGAIN },
54 { "line1", SOUND_MIXER_LINE1 },
55 { "line2", SOUND_MIXER_LINE2 },
56 { "line3", SOUND_MIXER_LINE3 },
57 { "digital1", SOUND_MIXER_DIGITAL1 },
58 { "digital2", SOUND_MIXER_DIGITAL2 },
59 { "digital3", SOUND_MIXER_DIGITAL3 },
60 { "phone-in", SOUND_MIXER_PHONEIN },
61 { "phone-out", SOUND_MIXER_PHONEOUT },
62 { "video", SOUND_MIXER_VIDEO },
63 { "radio", SOUND_MIXER_RADIO },
64 { "monitor", SOUND_MIXER_MONITOR },
65 { NULL, 0 }
66 };
67
68 typedef struct stereovolume
69 {
70 unsigned char left;
71 unsigned char right;
72
73 } StereoVolume;
74
75
76 typedef struct volctrl
77 {
78 int mixer_id;
79 StereoVolume volume;
80 int supported;
81 } VolumeControl;
82
83 VolumeControl Master;
84 int mixer_fd;
85
86 #include "volume.xpm"
87 /*#include "mask2.xbm"*/
88 #include "mark.xpm"
89
90 #define Center_X 24
91 #define Center_Y 26
92 #define Radius 13
93 #define min_a 0.75*M_PI
94 #define max_a 0.25*M_PI
95 #define WIZ M_PI/8
96
97 int ONLYSHAPE=0;
98 int ICONIFIED=0; /* default is not iconified */
99 int WITHDRAWN=0; /* default is not withdrawn */
100
101
102 /* X11 Variablen *************************************************************/
103 Display *dpy; /* welches DISPLAY */
104 Window Root; /* Hintergrund-Drawable */
105 int screen;
106
107 double Mark_Pos=M_PI/2;
108 int Pos=0;
109
110 int d_depth;
111 XSizeHints mysizehints;
112 XWMHints mywmhints;
113 Pixel back_pix, fore_pix;
114 GC NormalGC;
115 Window iconwin, win; /* My home is my window */
116 char *ProgName;
117 char *Geometry;
118 char Execute[] = "echo no program has been specified >/dev/console";
119 char *ERR_colorcells = "not enough free color cells\n";
120
121 Atom wm_delete_window;
122 Atom wm_protocols;
123
124 /* XPM Variablen *************************************************************/
125 typedef struct _XpmIcon {
126 Pixmap pixmap;
127 Pixmap mask;
128 XpmAttributes attributes;
129 } XpmIcon;
130
131 XpmIcon asMix, Mark;
132 time_t actualtime;
133
134 /* lokale Funktionen *********************************************************/
135 #define MW_EVENTS (ExposureMask | ButtonPressMask | StructureNotifyMask | \
136 ButtonMotionMask | PointerMotionMask)
137 #define FALSE 0
138 void GetXPM(void);
139 Pixel GetColor(char *name);
140 void RedrawWindow( XpmIcon *v);
141
142 /*****************************************************************************/
143 /*****************************************************************************/
144 static char *help_message[] = {
145 "where options include:",
146 " -exe <program> program to start on click",
147 " -geometry [+|-]x[+|-]y position of asmix",
148 " -shape without groundplate",
149 " -iconic start up as icon",
150 " -withdrawn start up in withdrawn mode",
151 " -channel <channel-id> specify the channel to control (def: volume)",
152 " -device <mixer-device> specify the mixer device (def: /dev/mixer)",
153 " -name <window-name> specify the name of the window (def: asmix)",
154 " -V version control",
155 NULL
156 };
157
version()158 void version()
159 {
160 printf("asmix: AfterStep sound mixer volume control 1.5\n");
161 }
162
usage()163 void usage()
164 {
165 char **cpp;
166
167 printf("usage: %s [-options ...] \n", ProgName);
168 for (cpp = help_message; *cpp; cpp++) {
169 printf("%s\n", *cpp);
170 }
171 printf("\n");
172 exit(1);
173 }
174
UpdatePos(double a,int flag)175 void UpdatePos(double a,int flag)
176 {
177 XCopyArea(dpy,asMix.pixmap,win,NormalGC,
178 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
179 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2,
180 Mark.attributes.width, Mark.attributes.height,
181 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
182 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
183 XCopyArea(dpy,asMix.pixmap,iconwin,NormalGC,
184 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
185 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2,
186 Mark.attributes.width, Mark.attributes.height,
187 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
188 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
189
190 if(flag==1){Mark_Pos=a;flag=0;}
191 else Mark_Pos=a;
192
193 Pos=Master.volume.left=Master.volume.right=(unsigned char)aToVol(Mark_Pos);
194
195 /* fprintf(stderr,"set_slider: updating mixer %i to %i:%i\n",Master.mixer_id,Master.volume.left,Master.volume.right);
196 */
197 if (ioctl(mixer_fd,MIXER_WRITE(Master.mixer_id),&Master.volume) == -1)
198 fprintf(stderr,"Error writing mixer in Handle_slider");
199
200 XCopyArea(dpy,Mark.pixmap,win,NormalGC,
201 0,0,Mark.attributes.width, Mark.attributes.height,
202 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
203 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
204 XCopyArea(dpy,Mark.pixmap,iconwin,NormalGC,
205 0,0,Mark.attributes.width, Mark.attributes.height,
206 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
207 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
208 }
209
aToVol(double a)210 int aToVol(double a)
211 {
212
213 int retval;
214
215 if ((a<0) || (a<M_PI/2)) a+=2*M_PI;
216
217 a-=3*M_PI/4;
218
219 retval=(int)(100*a/(1.5*M_PI));
220 if (retval<0) retval=0; else if (retval>100) retval=100;
221
222 return retval;
223 }
224
VolToa(int p)225 double VolToa(int p)
226 {
227 double NewPos;
228
229 NewPos=(float)p/100*3*M_PI/2+3*M_PI/4;
230 if ((NewPos>M_PI)&&(NewPos<2*M_PI)) NewPos-=2*M_PI;
231
232 return NewPos;
233 }
234
MouseMove(int x,int y)235 void MouseMove(int x, int y)
236 {
237 double X,Y;
238 double a;
239
240 X= (double)x - Center_X;
241 Y= (double)y - Center_Y;
242
243 if (sqrt(X*X+Y*Y)<=2) return;
244
245 a=atan2(Y,X);
246
247 if ((a<min_a) &&(a>max_a)) {
248 if ((Mark_Pos<-M_PI/2)||(Mark_Pos>M_PI/2)) a=min_a; else a=max_a;
249 }
250
251 UpdatePos(a,0);
252 }
253
254
sync_Control(VolumeControl * vcptr)255 static void sync_Control(VolumeControl *vcptr)
256 {
257 int portion;
258
259 /*if (!vcptr->supported)
260 return;*/
261 if (ioctl(mixer_fd,MIXER_READ(vcptr->mixer_id),&vcptr->volume) == -1)
262 perror("Error reading volumes in sync_slider");
263
264 portion=(vcptr->volume.left+vcptr->volume.right)/2;
265
266 /* fprintf(stderr,"%f : %i : %i\n",NewPos,Pos,portion);*/
267
268 if (Pos!=portion) {
269 UpdatePos(VolToa(portion),0);
270 Pos=portion;
271 }
272 }
273
274
main(int argc,char * argv[])275 int main(int argc,char *argv[])
276 {
277 int i;
278 unsigned int borderwidth ;
279 char *display_name = NULL;
280 char *wname = "asmix";
281 char *mixer_device = "/dev/mixer";
282 XGCValues gcv;
283 unsigned long gcm;
284 XEvent Event;
285 XTextProperty name;
286 XClassHint classHint;
287 Pixmap pixmask;
288 int status;
289
290 ProgName = argv[0];
291 Geometry = "";
292
293
294 Master.mixer_id = SOUND_MIXER_VOLUME;
295 Master.supported = SOUND_MIXER_VOLUME;
296
297 /* Parse command line options */
298 ProgName = argv[0];
299
300 for(i=1;i<argc;i++) {
301 char *arg= argv[i];
302
303 if (arg[0] == '-') {
304 switch(arg[1]) {
305 case 'e':
306 if(++i >=argc) usage();
307 strcpy(&Execute[0], argv[i]);
308 strcat(&Execute[0], " &");
309 continue;
310 case 's':
311 ONLYSHAPE=1;
312 continue;
313 case 'g':
314 if(++i >=argc) usage();
315 Geometry = argv[i];
316 continue;
317 case 'i':
318 ICONIFIED=1;
319 continue;
320 case 'w':
321 WITHDRAWN=1;
322 continue;
323 case 'c':
324 if(++i >=argc) usage();
325 {
326 int cur=0;
327 while(1)
328 {
329 if ( channel_idents[cur].channel_name == NULL )
330 usage();
331 if ( strcmp( channel_idents[cur].channel_name, argv[i] ) != 0 )
332 {
333 cur++;
334 continue;
335 }
336 Master.mixer_id = channel_idents[cur].channel_id;
337 Master.supported = channel_idents[cur].channel_id;
338 break;
339 }
340 }
341 continue;
342 case 'd':
343 if(++i >=argc) usage();
344 mixer_device=argv[i];
345 continue;
346 case 'n':
347 if(++i >=argc) usage();
348 wname=argv[i];
349 continue;
350 case 'V':
351 version();
352 exit(0);
353 default:
354 version();
355 usage();
356 }
357 }
358 }
359
360 mixer_fd = open (mixer_device, O_RDWR, 0);
361 if (mixer_fd < 0) {
362 fprintf (stderr,"asmix: Error opening mixer device %s", mixer_device);
363 exit (1);
364 }
365
366 if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &Master.supported) == -1)
367 Master.supported = 0xffff; /* Assume all are supported */
368 /* printf("Master.supported = 0x%x\n",Master.supported);*/
369
370
371 /* Open the display */
372 if (!(dpy = XOpenDisplay(display_name)))
373 {
374 fprintf(stderr,"asmix: can't open display %s\n",
375 XDisplayName(display_name));
376 exit (1);
377 }
378 screen= DefaultScreen(dpy);
379 Root = RootWindow(dpy, screen);
380 d_depth = DefaultDepth(dpy, screen);
381
382 /* Icon Daten nach XImage konvertieren */
383 GetXPM();
384
385 /* Create a window to hold the banner */
386 mysizehints.flags= USSize|USPosition;
387 mysizehints.x = 0;
388 mysizehints.y = 0;
389
390 back_pix = GetColor("grey");
391 fore_pix = GetColor("darkgrey");
392
393 XWMGeometry(dpy, screen, Geometry, NULL, (borderwidth =1), &mysizehints,
394 &mysizehints.x,&mysizehints.y,&mysizehints.width,&mysizehints.height, &i);
395
396 mysizehints.width = asMix.attributes.width;
397 mysizehints.height= asMix.attributes.height;
398
399 win = XCreateSimpleWindow(dpy,Root,mysizehints.x,mysizehints.y,
400 mysizehints.width,mysizehints.height,
401 borderwidth,fore_pix,back_pix);
402 iconwin = XCreateSimpleWindow(dpy,win,mysizehints.x,mysizehints.y,
403 mysizehints.width,mysizehints.height,
404 borderwidth,fore_pix,back_pix);
405
406
407 /* Set up the event for quitting the window */
408 wm_delete_window = XInternAtom(
409 dpy,
410 "WM_DELETE_WINDOW", /* atom_name */
411 False /* only_if_exists */
412 );
413 wm_protocols = XInternAtom(
414 dpy,
415 "WM_PROTOCOLS", /* atom_name */
416 False /* only_if_exists */
417 );
418 status = XSetWMProtocols(
419 dpy,
420 win,
421 &wm_delete_window,
422 1
423 );
424 status = XSetWMProtocols(
425 dpy,
426 iconwin,
427 &wm_delete_window,
428 1
429 );
430
431
432 /* Hints aktivieren */
433 XSetWMNormalHints(dpy, win, &mysizehints);
434 classHint.res_name = "asmix";
435 classHint.res_class = "asMix";
436 XSetClassHint(dpy, win, &classHint);
437
438 XSelectInput(dpy, win, MW_EVENTS);
439 XSelectInput(dpy, iconwin, MW_EVENTS);
440
441 if (XStringListToTextProperty(&wname, 1, &name) ==0) {
442 fprintf(stderr, "asmix: can't allocate window name [%s]\n", wname);
443 exit(-1);
444 }
445 XSetWMName(dpy, win, &name);
446 XSetWMName(dpy, iconwin, &name);
447
448 /* Create a GC for drawing */
449 gcm = GCForeground|GCBackground|GCGraphicsExposures;
450 gcv.foreground = fore_pix;
451 gcv.background = back_pix;
452 gcv.graphics_exposures = FALSE;
453 NormalGC = XCreateGC(dpy, Root, gcm, &gcv);
454
455 if (ONLYSHAPE) {
456 /* try to make shaped window here */
457 /* pixmask = XCreateBitmapFromData(dpy, win, mask_bits,
458 mask_width, mask_height);*/
459 XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, asMix.mask, ShapeSet);
460 XShapeCombineMask(dpy, iconwin, ShapeBounding, 0, 0, asMix.mask, ShapeSet);
461 }
462
463 mywmhints.initial_state =
464 WITHDRAWN ? WithdrawnState :
465 ICONIFIED ? IconicState : NormalState;
466 mywmhints.window_group = win;
467 mywmhints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
468 mywmhints.icon_window = iconwin;
469 mywmhints.icon_x = mysizehints.x;
470 mywmhints.icon_y = mysizehints.y;
471 XSetWMHints(dpy, win, &mywmhints);
472
473 status = XSetCommand(dpy, win, argv, argc);
474
475 XSetWindowBackgroundPixmap(dpy,win,asMix.pixmap);
476 XSetWindowBackgroundPixmap(dpy,iconwin,asMix.pixmap);
477
478 sync_Control(&Master);
479
480 XMapWindow(dpy,win);
481
482 RedrawWindow(&asMix);
483 while(1)
484 {
485 if (actualtime != time(0))
486 {
487 actualtime = time(0);
488
489 sync_Control(&Master);
490
491 }
492
493 /* read a packet */
494 while (XPending(dpy))
495 {
496 XNextEvent(dpy,&Event);
497 switch(Event.type)
498 {
499 case ClientMessage:
500 if ((Event.xclient.message_type == wm_protocols)
501 && (Event.xclient.data.l[0] == wm_delete_window))
502 {
503 XCloseDisplay(dpy);
504 exit(0);
505 }
506 break;
507 case Expose:
508 if(Event.xexpose.count == 0 )
509 RedrawWindow(&asMix);
510 break;
511 case ButtonPress:
512 if (Event.xbutton.button == Button1) {
513 MouseMove(Event.xbutton.x, Event.xbutton.y);
514 } else if (Event.xbutton.button == Button2) {
515 system(Execute);
516 }
517 else if (Event.xbutton.button == Button4) {
518 UpdatePos(WIZ,1);
519 }
520 else if (Event.xbutton.button == Button4) {
521 UpdatePos(-WIZ,1);
522 }
523 break;
524 case MotionNotify: {
525 Window Root, Child;
526 int root_x, root_y;
527 int win_x, win_y;
528 unsigned int mask;
529
530 if (XQueryPointer(dpy, win, &Root, &Child,
531 &root_x, &root_y, &win_x, &win_y, &mask)!=0) {
532 if (mask & Button1MotionMask)
533 MouseMove(Event.xbutton.x, Event.xbutton.y);
534 }
535 break;
536 }
537 case DestroyNotify:
538 XFreeGC(dpy, NormalGC);
539 XFlush(dpy);
540 /* fprintf(stderr,"DestroyMe?\n");*/
541 /* XDestroyWindow(dpy, win);
542 XDestroyWindow(dpy, iconwin);*/
543 XCloseDisplay(dpy);
544 exit(0);
545 default:
546 break;
547 }
548 }
549 #ifdef SYSV
550 poll((struct poll *) 0, (size_t) 0, 50);
551 #else
552 usleep(50000L); /* 50/100 sec */
553 #endif
554 }
555 return 0;
556 }
557 /****************************************************************************/
nocolor(char * a,char * b)558 void nocolor(char *a, char *b)
559 {
560 fprintf(stderr,"asmix: can't %s %s\n", a,b);
561 }
562 /****************************************************************************/
563 /* Konvertiere XPMIcons nach XImage */
GetXPM(void)564 void GetXPM(void)
565 {
566 static char **bg_xpm;
567 XColor col;
568 XWindowAttributes attributes;
569 int ret;
570
571 bg_xpm =ONLYSHAPE ? volume_xpm : volume_xpm;
572
573 /* for the colormap */
574 XGetWindowAttributes(dpy,Root,&attributes);
575
576 asMix.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
577 ret = XpmCreatePixmapFromData(dpy, Root, bg_xpm, &asMix.pixmap,
578 &asMix.mask, &asMix.attributes);
579 if(ret != XpmSuccess)
580 {fprintf(stderr, ERR_colorcells);exit(1);}
581
582
583 Mark.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions);
584 ret = XpmCreatePixmapFromData(dpy, Root, mark_xpm, &Mark.pixmap,
585 &Mark.mask, &Mark.attributes);
586 if(ret != XpmSuccess)
587 {fprintf(stderr, ERR_colorcells);exit(1);}
588
589 }
590 /****************************************************************************/
591 /* Removes expose events for a specific window from the queue */
flush_expose(Window w)592 int flush_expose (Window w)
593 {
594 XEvent dummy;
595 int i=0;
596
597 while (XCheckTypedWindowEvent (dpy, w, Expose, &dummy))i++;
598 return i;
599 }
600
601 /****************************************************************************/
602 /* Draws the icon window */
RedrawWindow(XpmIcon * v)603 void RedrawWindow( XpmIcon *v)
604 {
605 flush_expose (iconwin);
606
607 XCopyArea(dpy,Mark.pixmap,win,NormalGC,
608 0,0,Mark.attributes.width, Mark.attributes.height,
609 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
610 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
611
612 XCopyArea(dpy,Mark.pixmap,iconwin,NormalGC,
613 0,0,Mark.attributes.width, Mark.attributes.height,
614 Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2,
615 Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2);
616
617 }
618 /****************************************************************************/
GetColor(char * name)619 Pixel GetColor(char *name)
620 {
621 XColor color;
622 XWindowAttributes attributes;
623
624 XGetWindowAttributes(dpy,Root,&attributes);
625 color.pixel = 0;
626 if (!XParseColor (dpy, attributes.colormap, name, &color))
627 {
628 nocolor("parse",name);
629 }
630 else if(!XAllocColor (dpy, attributes.colormap, &color))
631 {
632 nocolor("alloc",name);
633 }
634 return color.pixel;
635 }
636