/* * asmix is the AfterStep sound volume control knob for X Windows. * Copyright (c) 1998 original author unknown * Copyright (c) 1998-2004 Albert "Tigr" Dorofeev * Copyright (c) 2000 John "wizgrav" Gravezas * * This software is distributed under GPL. For details see LICENSE file. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SOUNDCARD_H # include #endif #ifdef HAVE_LINUX_SOUNDCARD_H # include #endif typedef struct mixer_channel_ident_t { char * channel_name; int channel_id; } mixer_channel_ident; mixer_channel_ident channel_idents[] = { { "volume", SOUND_MIXER_VOLUME }, { "bass", SOUND_MIXER_BASS }, { "treble", SOUND_MIXER_TREBLE }, { "synth", SOUND_MIXER_SYNTH }, { "pcm", SOUND_MIXER_PCM }, { "speaker", SOUND_MIXER_SPEAKER }, { "line", SOUND_MIXER_LINE }, { "mic", SOUND_MIXER_MIC }, { "cd", SOUND_MIXER_CD }, { "imix", SOUND_MIXER_IMIX }, { "pcm2", SOUND_MIXER_ALTPCM }, { "record", SOUND_MIXER_RECLEV }, { "igain", SOUND_MIXER_IGAIN }, { "ogain", SOUND_MIXER_OGAIN }, { "line1", SOUND_MIXER_LINE1 }, { "line2", SOUND_MIXER_LINE2 }, { "line3", SOUND_MIXER_LINE3 }, { "digital1", SOUND_MIXER_DIGITAL1 }, { "digital2", SOUND_MIXER_DIGITAL2 }, { "digital3", SOUND_MIXER_DIGITAL3 }, { "phone-in", SOUND_MIXER_PHONEIN }, { "phone-out", SOUND_MIXER_PHONEOUT }, { "video", SOUND_MIXER_VIDEO }, { "radio", SOUND_MIXER_RADIO }, { "monitor", SOUND_MIXER_MONITOR }, { NULL, 0 } }; typedef struct stereovolume { unsigned char left; unsigned char right; } StereoVolume; typedef struct volctrl { int mixer_id; StereoVolume volume; int supported; } VolumeControl; VolumeControl Master; int mixer_fd; #include "volume.xpm" /*#include "mask2.xbm"*/ #include "mark.xpm" #define Center_X 24 #define Center_Y 26 #define Radius 13 #define min_a 0.75*M_PI #define max_a 0.25*M_PI #define WIZ M_PI/8 int ONLYSHAPE=0; int ICONIFIED=0; /* default is not iconified */ int WITHDRAWN=0; /* default is not withdrawn */ /* X11 Variablen *************************************************************/ Display *dpy; /* welches DISPLAY */ Window Root; /* Hintergrund-Drawable */ int screen; double Mark_Pos=M_PI/2; int Pos=0; int d_depth; XSizeHints mysizehints; XWMHints mywmhints; Pixel back_pix, fore_pix; GC NormalGC; Window iconwin, win; /* My home is my window */ char *ProgName; char *Geometry; char Execute[] = "echo no program has been specified >/dev/console"; char *ERR_colorcells = "not enough free color cells\n"; Atom wm_delete_window; Atom wm_protocols; /* XPM Variablen *************************************************************/ typedef struct _XpmIcon { Pixmap pixmap; Pixmap mask; XpmAttributes attributes; } XpmIcon; XpmIcon asMix, Mark; time_t actualtime; /* lokale Funktionen *********************************************************/ #define MW_EVENTS (ExposureMask | ButtonPressMask | StructureNotifyMask | \ ButtonMotionMask | PointerMotionMask) #define FALSE 0 void GetXPM(void); Pixel GetColor(char *name); void RedrawWindow( XpmIcon *v); /*****************************************************************************/ /*****************************************************************************/ static char *help_message[] = { "where options include:", " -exe program to start on click", " -geometry [+|-]x[+|-]y position of asmix", " -shape without groundplate", " -iconic start up as icon", " -withdrawn start up in withdrawn mode", " -channel specify the channel to control (def: volume)", " -device specify the mixer device (def: /dev/mixer)", " -name specify the name of the window (def: asmix)", " -V version control", NULL }; void version() { printf("asmix: AfterStep sound mixer volume control 1.5\n"); } void usage() { char **cpp; printf("usage: %s [-options ...] \n", ProgName); for (cpp = help_message; *cpp; cpp++) { printf("%s\n", *cpp); } printf("\n"); exit(1); } void UpdatePos(double a,int flag) { XCopyArea(dpy,asMix.pixmap,win,NormalGC, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2, Mark.attributes.width, Mark.attributes.height, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2); XCopyArea(dpy,asMix.pixmap,iconwin,NormalGC, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2, Mark.attributes.width, Mark.attributes.height, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2); if(flag==1){Mark_Pos=a;flag=0;} else Mark_Pos=a; Pos=Master.volume.left=Master.volume.right=(unsigned char)aToVol(Mark_Pos); /* fprintf(stderr,"set_slider: updating mixer %i to %i:%i\n",Master.mixer_id,Master.volume.left,Master.volume.right); */ if (ioctl(mixer_fd,MIXER_WRITE(Master.mixer_id),&Master.volume) == -1) fprintf(stderr,"Error writing mixer in Handle_slider"); XCopyArea(dpy,Mark.pixmap,win,NormalGC, 0,0,Mark.attributes.width, Mark.attributes.height, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2); XCopyArea(dpy,Mark.pixmap,iconwin,NormalGC, 0,0,Mark.attributes.width, Mark.attributes.height, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2); } int aToVol(double a) { int retval; if ((a<0) || (a100) retval=100; return retval; } double VolToa(int p) { double NewPos; NewPos=(float)p/100*3*M_PI/2+3*M_PI/4; if ((NewPos>M_PI)&&(NewPos<2*M_PI)) NewPos-=2*M_PI; return NewPos; } void MouseMove(int x, int y) { double X,Y; double a; X= (double)x - Center_X; Y= (double)y - Center_Y; if (sqrt(X*X+Y*Y)<=2) return; a=atan2(Y,X); if ((amax_a)) { if ((Mark_Pos<-M_PI/2)||(Mark_Pos>M_PI/2)) a=min_a; else a=max_a; } UpdatePos(a,0); } static void sync_Control(VolumeControl *vcptr) { int portion; /*if (!vcptr->supported) return;*/ if (ioctl(mixer_fd,MIXER_READ(vcptr->mixer_id),&vcptr->volume) == -1) perror("Error reading volumes in sync_slider"); portion=(vcptr->volume.left+vcptr->volume.right)/2; /* fprintf(stderr,"%f : %i : %i\n",NewPos,Pos,portion);*/ if (Pos!=portion) { UpdatePos(VolToa(portion),0); Pos=portion; } } int main(int argc,char *argv[]) { int i; unsigned int borderwidth ; char *display_name = NULL; char *wname = "asmix"; char *mixer_device = "/dev/mixer"; XGCValues gcv; unsigned long gcm; XEvent Event; XTextProperty name; XClassHint classHint; Pixmap pixmask; int status; ProgName = argv[0]; Geometry = ""; Master.mixer_id = SOUND_MIXER_VOLUME; Master.supported = SOUND_MIXER_VOLUME; /* Parse command line options */ ProgName = argv[0]; for(i=1;i=argc) usage(); strcpy(&Execute[0], argv[i]); strcat(&Execute[0], " &"); continue; case 's': ONLYSHAPE=1; continue; case 'g': if(++i >=argc) usage(); Geometry = argv[i]; continue; case 'i': ICONIFIED=1; continue; case 'w': WITHDRAWN=1; continue; case 'c': if(++i >=argc) usage(); { int cur=0; while(1) { if ( channel_idents[cur].channel_name == NULL ) usage(); if ( strcmp( channel_idents[cur].channel_name, argv[i] ) != 0 ) { cur++; continue; } Master.mixer_id = channel_idents[cur].channel_id; Master.supported = channel_idents[cur].channel_id; break; } } continue; case 'd': if(++i >=argc) usage(); mixer_device=argv[i]; continue; case 'n': if(++i >=argc) usage(); wname=argv[i]; continue; case 'V': version(); exit(0); default: version(); usage(); } } } mixer_fd = open (mixer_device, O_RDWR, 0); if (mixer_fd < 0) { fprintf (stderr,"asmix: Error opening mixer device %s", mixer_device); exit (1); } if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &Master.supported) == -1) Master.supported = 0xffff; /* Assume all are supported */ /* printf("Master.supported = 0x%x\n",Master.supported);*/ /* Open the display */ if (!(dpy = XOpenDisplay(display_name))) { fprintf(stderr,"asmix: can't open display %s\n", XDisplayName(display_name)); exit (1); } screen= DefaultScreen(dpy); Root = RootWindow(dpy, screen); d_depth = DefaultDepth(dpy, screen); /* Icon Daten nach XImage konvertieren */ GetXPM(); /* Create a window to hold the banner */ mysizehints.flags= USSize|USPosition; mysizehints.x = 0; mysizehints.y = 0; back_pix = GetColor("grey"); fore_pix = GetColor("darkgrey"); XWMGeometry(dpy, screen, Geometry, NULL, (borderwidth =1), &mysizehints, &mysizehints.x,&mysizehints.y,&mysizehints.width,&mysizehints.height, &i); mysizehints.width = asMix.attributes.width; mysizehints.height= asMix.attributes.height; win = XCreateSimpleWindow(dpy,Root,mysizehints.x,mysizehints.y, mysizehints.width,mysizehints.height, borderwidth,fore_pix,back_pix); iconwin = XCreateSimpleWindow(dpy,win,mysizehints.x,mysizehints.y, mysizehints.width,mysizehints.height, borderwidth,fore_pix,back_pix); /* Set up the event for quitting the window */ wm_delete_window = XInternAtom( dpy, "WM_DELETE_WINDOW", /* atom_name */ False /* only_if_exists */ ); wm_protocols = XInternAtom( dpy, "WM_PROTOCOLS", /* atom_name */ False /* only_if_exists */ ); status = XSetWMProtocols( dpy, win, &wm_delete_window, 1 ); status = XSetWMProtocols( dpy, iconwin, &wm_delete_window, 1 ); /* Hints aktivieren */ XSetWMNormalHints(dpy, win, &mysizehints); classHint.res_name = "asmix"; classHint.res_class = "asMix"; XSetClassHint(dpy, win, &classHint); XSelectInput(dpy, win, MW_EVENTS); XSelectInput(dpy, iconwin, MW_EVENTS); if (XStringListToTextProperty(&wname, 1, &name) ==0) { fprintf(stderr, "asmix: can't allocate window name [%s]\n", wname); exit(-1); } XSetWMName(dpy, win, &name); XSetWMName(dpy, iconwin, &name); /* Create a GC for drawing */ gcm = GCForeground|GCBackground|GCGraphicsExposures; gcv.foreground = fore_pix; gcv.background = back_pix; gcv.graphics_exposures = FALSE; NormalGC = XCreateGC(dpy, Root, gcm, &gcv); if (ONLYSHAPE) { /* try to make shaped window here */ /* pixmask = XCreateBitmapFromData(dpy, win, mask_bits, mask_width, mask_height);*/ XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, asMix.mask, ShapeSet); XShapeCombineMask(dpy, iconwin, ShapeBounding, 0, 0, asMix.mask, ShapeSet); } mywmhints.initial_state = WITHDRAWN ? WithdrawnState : ICONIFIED ? IconicState : NormalState; mywmhints.window_group = win; mywmhints.flags = StateHint | IconWindowHint | IconPositionHint | WindowGroupHint; mywmhints.icon_window = iconwin; mywmhints.icon_x = mysizehints.x; mywmhints.icon_y = mysizehints.y; XSetWMHints(dpy, win, &mywmhints); status = XSetCommand(dpy, win, argv, argc); XSetWindowBackgroundPixmap(dpy,win,asMix.pixmap); XSetWindowBackgroundPixmap(dpy,iconwin,asMix.pixmap); sync_Control(&Master); XMapWindow(dpy,win); RedrawWindow(&asMix); while(1) { if (actualtime != time(0)) { actualtime = time(0); sync_Control(&Master); } /* read a packet */ while (XPending(dpy)) { XNextEvent(dpy,&Event); switch(Event.type) { case ClientMessage: if ((Event.xclient.message_type == wm_protocols) && (Event.xclient.data.l[0] == wm_delete_window)) { XCloseDisplay(dpy); exit(0); } break; case Expose: if(Event.xexpose.count == 0 ) RedrawWindow(&asMix); break; case ButtonPress: if (Event.xbutton.button == Button1) { MouseMove(Event.xbutton.x, Event.xbutton.y); } else if (Event.xbutton.button == Button2) { system(Execute); } else if (Event.xbutton.button == Button4) { UpdatePos(WIZ,1); } else if (Event.xbutton.button == Button4) { UpdatePos(-WIZ,1); } break; case MotionNotify: { Window Root, Child; int root_x, root_y; int win_x, win_y; unsigned int mask; if (XQueryPointer(dpy, win, &Root, &Child, &root_x, &root_y, &win_x, &win_y, &mask)!=0) { if (mask & Button1MotionMask) MouseMove(Event.xbutton.x, Event.xbutton.y); } break; } case DestroyNotify: XFreeGC(dpy, NormalGC); XFlush(dpy); /* fprintf(stderr,"DestroyMe?\n");*/ /* XDestroyWindow(dpy, win); XDestroyWindow(dpy, iconwin);*/ XCloseDisplay(dpy); exit(0); default: break; } } #ifdef SYSV poll((struct poll *) 0, (size_t) 0, 50); #else usleep(50000L); /* 50/100 sec */ #endif } return 0; } /****************************************************************************/ void nocolor(char *a, char *b) { fprintf(stderr,"asmix: can't %s %s\n", a,b); } /****************************************************************************/ /* Konvertiere XPMIcons nach XImage */ void GetXPM(void) { static char **bg_xpm; XColor col; XWindowAttributes attributes; int ret; bg_xpm =ONLYSHAPE ? volume_xpm : volume_xpm; /* for the colormap */ XGetWindowAttributes(dpy,Root,&attributes); asMix.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions); ret = XpmCreatePixmapFromData(dpy, Root, bg_xpm, &asMix.pixmap, &asMix.mask, &asMix.attributes); if(ret != XpmSuccess) {fprintf(stderr, ERR_colorcells);exit(1);} Mark.attributes.valuemask |= (XpmReturnPixels | XpmReturnExtensions); ret = XpmCreatePixmapFromData(dpy, Root, mark_xpm, &Mark.pixmap, &Mark.mask, &Mark.attributes); if(ret != XpmSuccess) {fprintf(stderr, ERR_colorcells);exit(1);} } /****************************************************************************/ /* Removes expose events for a specific window from the queue */ int flush_expose (Window w) { XEvent dummy; int i=0; while (XCheckTypedWindowEvent (dpy, w, Expose, &dummy))i++; return i; } /****************************************************************************/ /* Draws the icon window */ void RedrawWindow( XpmIcon *v) { flush_expose (iconwin); XCopyArea(dpy,Mark.pixmap,win,NormalGC, 0,0,Mark.attributes.width, Mark.attributes.height, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2); XCopyArea(dpy,Mark.pixmap,iconwin,NormalGC, 0,0,Mark.attributes.width, Mark.attributes.height, Center_X+Radius*cos(Mark_Pos)-Mark.attributes.width/2, Center_Y+Radius*sin(Mark_Pos)-Mark.attributes.height/2); } /****************************************************************************/ Pixel GetColor(char *name) { XColor color; XWindowAttributes attributes; XGetWindowAttributes(dpy,Root,&attributes); color.pixel = 0; if (!XParseColor (dpy, attributes.colormap, name, &color)) { nocolor("parse",name); } else if(!XAllocColor (dpy, attributes.colormap, &color)) { nocolor("alloc",name); } return color.pixel; }