// wmamixer - An ALSA/OSS mixer designed for WindowMaker with scrollwheel support // Copyright (C) 2015 Roman Dobosz // Copyright (C) 2003 Damian Kramer // Copyright (C) 1998 Sam Hawker // // This software comes with ABSOLUTELY NO WARRANTY // This software is free software, and you are welcome to redistribute it // under certain conditions // See the README file for a more complete notice. #include "wmamixer.h" int main(int argc, char **argv) { XGCValues gcv; unsigned long gcm; XpmAttributes xpmattr; scanArgs(argc, argv); initXWin(argc, argv); gcm = GCGraphicsExposures; gcv.graphics_exposures = false; gc_gc = XCreateGC(d_display, w_root, gcm, &gcv); color[0] = mixColor(ledcolor, 0, backcolor, 100); color[1] = mixColor(ledcolor, 100, backcolor, 0); color[2] = mixColor(ledcolor, 60, backcolor, 40); color[3] = mixColor(ledcolor, 25, backcolor, 75); XpmColorSymbol xpmcsym[4] = { {"back_color", NULL, color[0]}, {"led_color_high", NULL, color[1]}, {"led_color_med", NULL, color[2]}, {"led_color_low", NULL, color[3]} }; xpmattr.numsymbols = 4; xpmattr.colorsymbols = xpmcsym; xpmattr.exactColors = false; xpmattr.closeness = 40000; xpmattr.valuemask = XpmColorSymbols | XpmExactColors | XpmCloseness; XpmCreatePixmapFromData(d_display, w_root, wmamixer_xpm, &pm_main, &pm_mask, &xpmattr); XpmCreatePixmapFromData(d_display, w_root, tile_xpm, &pm_tile, NULL, &xpmattr); XpmCreatePixmapFromData(d_display, w_root, icons_xpm, &pm_icon, NULL, &xpmattr); XpmCreatePixmapFromData(d_display, w_root, digits_xpm, &pm_digits, NULL, &xpmattr); XpmCreatePixmapFromData(d_display, w_root, chars_xpm, &pm_chars, NULL, &xpmattr); pm_disp = XCreatePixmap(d_display, w_root, 64, 64, DefaultDepth(d_display, DefaultScreen(d_display))); if (wmaker || ushape || astep) { XShapeCombineMask(d_display, w_activewin, ShapeBounding, winsize/2-32, winsize/2-32, pm_mask, ShapeSet); } else { XCopyArea(d_display, pm_tile, pm_disp, gc_gc, 0, 0, 64, 64, 0, 0); } XSetClipMask(d_display, gc_gc, pm_mask); XCopyArea(d_display, pm_main, pm_disp, gc_gc, 0, 0, 64, 64, 0, 0); XSetClipMask(d_display, gc_gc, None); mix = Mixer_create(); if (mix->devices_no == 0) { fprintf(stderr, "%s: Sorry, no supported channels found.\n", NAME); } else { checkVol(true); XEvent xev; XSelectInput(d_display, w_activewin, ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask); XMapWindow(d_display, w_main); bool done = false; while (!done) { while (XPending(d_display)) { XNextEvent(d_display, &xev); switch (xev.type) { case Expose: repaint(); break; case ButtonPress: pressEvent(&xev.xbutton); break; case ButtonRelease: releaseEvent(); break; case MotionNotify: motionEvent(&xev.xmotion); break; case ClientMessage: if (xev.xclient.data.l[0] == (int) deleteWin) done = true; break; } } if (btnstate & (BTNPREV | BTNNEXT)) { rpttimer++; if (rpttimer >= RPTINTERVAL) { if (btnstate & BTNNEXT) curchannel++; else curchannel--; if (curchannel < 0) curchannel = mix->devices_no - 1; if (curchannel >= mix->devices_no) curchannel = 0; checkVol(true); rpttimer = 0; } } else { checkVol(false); } if (text_counter) { text_counter--; if (!text_counter) { drawVolLevel(); repaint(); } } XFlush(d_display); if (mix->backend == BACKEND_ALSA) snd_mixer_handle_events(mix->handle); usleep(50000); } } XFreeGC(d_display, gc_gc); XFreePixmap(d_display, pm_main); XFreePixmap(d_display, pm_tile); XFreePixmap(d_display, pm_disp); XFreePixmap(d_display, pm_mask); XFreePixmap(d_display, pm_icon); XFreePixmap(d_display, pm_digits); XFreePixmap(d_display, pm_chars); freeXWin(); Selem_destroy(); Mixer_destroy(mix); return 0; } /* Mixer struct support functions */ struct Mixer *Mixer_create() { struct Mixer *mixer = malloc(sizeof(struct Mixer)); int err; const char *name; slideCaptureMono capabilities; assert(mixer != NULL); mixer->devices_no = 0; mixer->backend = BACKEND_ALSA; mixer->oss_fd_count = 0; for(int i=0;ioss_fds[i] = -1; mixer->oss_paths[i] = NULL; } // --- Try ALSA first unless forced OSS --- if (!force_oss) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *elem; snd_mixer_selem_id_alloca(&sid); if ((err = snd_mixer_open(&mixer->handle, 0)) < 0) { fprintf(stderr, "Mixer %s open error: %s\n", card, snd_strerror(err)); } else if ((err = snd_mixer_attach(mixer->handle, card)) < 0) { fprintf(stderr, "Mixer attach %s error: %s\n", card, snd_strerror(err)); snd_mixer_close(mixer->handle); mixer->handle = NULL; } else if ((err = snd_mixer_selem_register(mixer->handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) { fprintf(stderr, "Mixer register error: %s\n", snd_strerror(err)); snd_mixer_close(mixer->handle); mixer->handle = NULL; } else { err = snd_mixer_load(mixer->handle); if (err < 0) { fprintf(stderr, "Mixer %s load error: %s\n", card, snd_strerror(err)); snd_mixer_close(mixer->handle); mixer->handle = NULL; } else { for (elem = snd_mixer_first_elem(mixer->handle); elem; elem = snd_mixer_elem_next(elem)) { snd_mixer_selem_get_id(elem, sid); if (!snd_mixer_selem_is_active(elem)) continue; capabilities = Mixer_get_capabilities(elem); if (!capabilities.isvolume) continue; name = snd_mixer_selem_id_get_name(sid); selems[mixer->devices_no] = malloc(sizeof(struct Selem)); memset(selems[mixer->devices_no], 0, sizeof(struct Selem)); selems[mixer->devices_no]->elem = elem; selems[mixer->devices_no]->capture = capabilities.capture; selems[mixer->devices_no]->oss_chan = -1; selems[mixer->devices_no]->oss_dev_idx = -1; Mixer_set_limits(elem, selems[mixer->devices_no]); Mixer_set_selem_props(mixer, selems[mixer->devices_no], name); Mixer_set_channels(selems[mixer->devices_no]); mixer->devices_no++; if (mixer->devices_no == 32) { break; } } if (mixer->devices_no > 0) return mixer; } } } // --- Fallback to OSS (or forced) --- fprintf(stderr, "%s: Using OSS backend\n", NAME); mixer->backend = BACKEND_OSS; // Determine device list: command line args or default int dev_count = g_oss_dev_count; char **dev_list = g_oss_dev_list; char *default_dev = "/dev/mixer"; if (dev_count == 0) { dev_count = 1; dev_list = &default_dev; } for (int d = 0; d < dev_count && d < MAX_OSS_DEVS; d++) { int fd = open(dev_list[d], O_RDWR | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "%s: Warning: Cannot open OSS mixer %s: %s\n", NAME, dev_list[d], strerror(errno)); continue; } mixer->oss_fds[mixer->oss_fd_count] = fd; mixer->oss_paths[mixer->oss_fd_count] = strdup(dev_list[d]); mixer->oss_fd_count++; } if (mixer->oss_fd_count == 0) { fprintf(stderr, "%s: No OSS mixer devices could be opened.\n", NAME); free(mixer); exit(1); } // Enumerate channels from all opened devices if (!Mixer_oss_enumerate(mixer)) { fprintf(stderr, "%s: No supported OSS channels found on any device.\n", NAME); } return mixer; } void Mixer_set_selem_props(struct Mixer *mixer, struct Selem *selem, const char *name) { const char *pcm = "PCM", *mic = "Mic", *cap = "Capture", *boost = "Boost", *line = "Line", *aux = "Aux"; // OSS elements have names set in Mixer_oss_enumerate if (mixer->backend == BACKEND_OSS) return; if (strcmp("Master", name) == 0) { selem->name = strdup("mstr"); selem->iconIndex = 0; } else if (strstr(pcm, name)) { selem->iconIndex = 1; if (strcmp(pcm, name) == 0) selem->name = strdup("pcm "); else Selem_set_name(selem, "pcm", &namesCount.pcm); } else if (strstr("Headphone", name)) { selem->name = strdup("hdph"); selem->iconIndex = 8; } else if (strstr("Beep", name) || strstr("PC Speaker", name)) { selem->name = strdup("beep"); selem->iconIndex = 7; } else if (strstr("Digital", name)) { selem->iconIndex = selem->capture ? 3 : 15; selem->name = strdup("digt"); } else if (strstr("Bass", name)) { selem->name = strdup("bass"); selem->iconIndex = 9; } else if (strstr("Treble", name)) { selem->name = strdup("trbl"); selem->iconIndex = 10; } else if (strstr("Synth", name)) { selem->name = strdup("synt"); selem->iconIndex = 11; } else if (strstr("CD", name)) { selem->name = strdup(" cd "); selem->iconIndex = 12; } else if (strstr("Phone", name)) { selem->name = strdup("phne"); selem->iconIndex = 13; } else if (strstr("Video", name)) { selem->name = strdup("vdeo"); selem->iconIndex = 14; } else if (strstr(mic, name)) { if (strcmp(mic, name) == 0) { selem->name = strdup("mic "); } else { if (strstr(boost, name)) Selem_set_name(selem, "mib", &namesCount.micb); else Selem_set_name(selem, "mic", &namesCount.mic); } selem->iconIndex = 4; } else if (strstr(aux, name)) { if (strcmp(aux, name) == 0) { selem->name = strdup("aux "); } else { Selem_set_name(selem, "aux", &namesCount.aux); } selem->iconIndex = 5; } else if (strstr(line, name)) { if (strcmp(line, name) == 0) { selem->name = strdup("line"); } else { if (strstr(boost, name)) Selem_set_name(selem, "lnb", &namesCount.lineb); else Selem_set_name(selem, "lin", &namesCount.line); } selem->iconIndex = 5; } else if (strstr(cap, name)) { if (strcmp(cap, name) == 0) { selem->name = strdup("cap "); } else { Selem_set_name(selem, "cap", &namesCount.capt); } selem->iconIndex = 3; } else { namesCount.vol++; selem->iconIndex = 6; Selem_set_name(selem, name, &namesCount.vol); } } void Selem_set_name(struct Selem *selem, const char *name, short unsigned int *count) { char new_name[5], buf[4]; if (*count > 10) { snprintf(new_name, sizeof(new_name), "%s", name); } else { snprintf(buf, sizeof(buf), "%s", name); snprintf(new_name, sizeof(new_name), "%s%hu", buf, *count); } selem->name = strdup(new_name); *count = *count + 1; } void Mixer_set_channels(struct Selem *selem) { int idx; snd_mixer_selem_channel_id_t chn; selem->stereo = true; if (snd_mixer_selem_has_playback_volume(selem->elem)) { if (snd_mixer_selem_is_playback_mono(selem->elem)) { selem->stereo = false; selem->channels[0] = SND_MIXER_SCHN_MONO; selem->channels[1] = SND_MIXER_SCHN_MONO; } else { idx = 0; for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { if (!snd_mixer_selem_has_playback_channel(selem->elem, chn)) continue; selem->channels[idx] = chn; idx++; if (idx == 2) break; } } } if (snd_mixer_selem_has_capture_volume(selem->elem)) { if (snd_mixer_selem_is_capture_mono(selem->elem)) { selem->stereo = false; selem->channels[0] = SND_MIXER_SCHN_MONO; selem->channels[1] = SND_MIXER_SCHN_MONO; } else { idx = 0; for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { if (!snd_mixer_selem_has_capture_channel(selem->elem, chn)) continue; selem->channels[idx] = chn; idx++; if (idx == 2) break; } } } } slideCaptureMono Mixer_get_capabilities(snd_mixer_elem_t *elem) { slideCaptureMono retval; retval.mono = false; retval.capture = false; retval.isvolume = false; if (snd_mixer_selem_has_common_volume(elem)) { retval.isvolume = true; if (snd_mixer_selem_has_playback_volume_joined(elem) || snd_mixer_selem_is_playback_mono(elem) ) retval.mono = true; } else { if (snd_mixer_selem_has_playback_volume(elem)) { retval.isvolume = true; if (snd_mixer_selem_has_playback_volume_joined(elem) || snd_mixer_selem_is_playback_mono(elem)) retval.mono = true; } if (snd_mixer_selem_has_capture_volume(elem)) { retval.isvolume = true; retval.capture = true; if (snd_mixer_selem_has_capture_volume_joined(elem) || snd_mixer_selem_is_playback_mono(elem)) retval.mono = true; } } return retval; } void Mixer_set_limits(snd_mixer_elem_t *elem, struct Selem *selem) { long min = 0, max = 0; if (snd_mixer_selem_has_common_volume(elem)) { snd_mixer_selem_get_playback_volume_range(elem, &min, &max); } else { if (snd_mixer_selem_has_capture_volume(elem)) snd_mixer_selem_get_capture_volume_range(elem, &min, &max); if (snd_mixer_selem_has_playback_volume(elem)) snd_mixer_selem_get_playback_volume_range(elem, &min, &max); } selem->min = min; selem->max = max; } static int convert_prange(long val, long min, long max) { long range = max - min; int tmp; if (range == 0) return 0; val -= min; tmp = rint((double)val/(double)range * 100); return tmp; } int Mixer_get_volume(int current, int channelIndex) { struct Selem *selem = selems[current]; if (mix->backend == BACKEND_OSS) { int fd = mix->oss_fds[selem->oss_dev_idx]; return Mixer_oss_get_volume(fd, selem->oss_chan); } long raw; int volume; if (!selem->capture) { snd_mixer_selem_get_playback_volume(selem->elem, selem->channels[channelIndex], &raw); } else { snd_mixer_selem_get_capture_volume(selem->elem, selem->channels[channelIndex], &raw); } volume = convert_prange(raw, selem->min, selem->max); return volume; } void Mixer_set_volume(int current, int channelIndex, int value) { struct Selem *selem = selems[current]; if (mix->backend == BACKEND_OSS) { int fd = mix->oss_fds[selem->oss_dev_idx]; Mixer_oss_set_volume(fd, selem->oss_chan, value); return; } long raw; raw = (long)convert_prange1(value, selem->min, selem->max); if (!selem->capture) { snd_mixer_selem_set_playback_volume(selem->elem, selem->channels[channelIndex], raw); } else { snd_mixer_selem_set_capture_volume(selem->elem, selem->channels[channelIndex], raw); } } void Mixer_set_left(int current, int value) { Mixer_set_volume(current, 0, value); } void Mixer_set_right(int current, int value) { Mixer_set_volume(current, 1, value); } int Mixer_read_left(int current) { return Mixer_get_volume(current, 0); } int Mixer_read_right(int current) { return Mixer_get_volume(current, 1); } void Mixer_destroy(struct Mixer *mixer) { assert(mixer != NULL); if (mixer->backend == BACKEND_ALSA && mixer->handle) snd_mixer_close(mixer->handle); if (mixer->backend == BACKEND_OSS) { for (int d = 0; d < mixer->oss_fd_count; d++) { if (mixer->oss_fds[d] >= 0) close(mixer->oss_fds[d]); free(mixer->oss_paths[d]); } } free(mixer); } void Selem_destroy() { int i; for (i = 0; i < mix->devices_no; i++) { free(selems[i]->name); free(selems[i]); } } // ------------------------------------------------------------ // OSS Implementation // ------------------------------------------------------------ static const struct { const char *name; int idx; int icon; const char *short_name; } oss_map[] = { {"vol", SOUND_MIXER_VOLUME, 0, "vol"}, {"pcm", SOUND_MIXER_PCM, 1, "pcm"}, {"speaker", SOUND_MIXER_SPEAKER, 6, "spk"}, {"line", SOUND_MIXER_LINE, 5, "lin"}, {"mic", SOUND_MIXER_MIC, 4, "mic"}, {"cd", SOUND_MIXER_CD, 12, "cd "}, {"reclev", SOUND_MIXER_RECLEV, 3, "rec"}, {"igain", SOUND_MIXER_IGAIN, 3, "iga"}, {"ogain", SOUND_MIXER_OGAIN, 6, "oga"}, {"line1", SOUND_MIXER_LINE1, 5, "ln1"}, {"line2", SOUND_MIXER_LINE2, 5, "ln2"}, {"line3", SOUND_MIXER_LINE3, 5, "ln3"}, {"dig1", SOUND_MIXER_DIGITAL1, 15, "dg1"}, {"dig2", SOUND_MIXER_DIGITAL2, 15, "dg2"}, {"dig3", SOUND_MIXER_DIGITAL3, 15, "dg3"}, {"phin", SOUND_MIXER_PHONEIN, 13, "phn"}, {"phout", SOUND_MIXER_PHONEOUT, 13, "pho"}, {"video", SOUND_MIXER_VIDEO, 14, "vid"}, {"radio", SOUND_MIXER_RADIO, 14, "rad"}, {"monitor", SOUND_MIXER_MONITOR, 6, "mon"}, }; bool Mixer_oss_enumerate(struct Mixer *mixer) { bool found_any = false; for (int d = 0; d < mixer->oss_fd_count; d++) { int fd = mixer->oss_fds[d]; int devmask = 0, recmask = 0, recsrc = 0; if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) continue; ioctl(fd, SOUND_MIXER_READ_RECMASK, &recmask); ioctl(fd, SOUND_MIXER_READ_RECSRC, &recsrc); const char *path = mixer->oss_paths[d]; const char *num_str = ""; if (path) { const char *p = path + strlen(path) - 1; while (p >= path && isdigit(*p)) p--; if (p < path + strlen(path) - 1) { num_str = p + 1; } } for (size_t i = 0; i < sizeof(oss_map)/sizeof(oss_map[0]); i++) { int idx = oss_map[i].idx; if (!(devmask & (1 << idx))) continue; struct Selem *s = malloc(sizeof(struct Selem)); memset(s, 0, sizeof(*s)); char display_name[8]; char base[5]; strncpy(base, oss_map[i].short_name, 4); base[4] = '\0'; int blen = strlen(base); while (blen > 0 && base[blen-1] == ' ') base[--blen] = '\0'; snprintf(display_name, sizeof(display_name), "%s%s", base, num_str); s->name = strdup(display_name); s->iconIndex = oss_map[i].icon; s->oss_chan = idx; s->oss_dev_idx = d; s->oss_devmask = devmask; s->stereo = true; s->capture = (recmask & (1 << idx)) != 0; s->elem = NULL; selems[mixer->devices_no++] = s; found_any = true; if (mixer->devices_no >= 32) return found_any; } } return found_any; } int Mixer_oss_get_volume(int fd, int chan) { int vol = 0; // Try stereo ioctl (MIXER_READ with | (1<<8) for stereo) if (ioctl(fd, MIXER_READ(chan | (1 << 8)), &vol) == 0) { return (vol & 0x7f) * 100 / 127; } // Fallback to legacy mono ioctl if (ioctl(fd, SOUND_MIXER_READ_VOLUME, &vol) == 0) { return (vol & 0x7f) * 100 / 127; } return 0; } int Mixer_oss_set_volume(int fd, int chan, int value) { int vol = (value * 127) / 100; if (vol < 0) vol = 0; if (vol > 127) vol = 127; int stereo_vol = vol | (vol << 8); if (ioctl(fd, MIXER_WRITE(chan | (1 << 8)), &stereo_vol) == 0) return 0; return ioctl(fd, SOUND_MIXER_WRITE_VOLUME, &vol); } void initXWin(int argc, char **argv) { bool pos; winsize = astep ? ASTEPSIZE : NORMSIZE; XWMHints wmhints; XSizeHints shints; if ((d_display = XOpenDisplay(display)) == NULL) { fprintf(stderr, "%s: Unable to open X display '%s'.\n", NAME, XDisplayName(display)); exit(1); } _XA_GNUSTEP_WM_FUNC = XInternAtom(d_display, "_GNUSTEP_WM_FUNCTION", false); deleteWin = XInternAtom(d_display, "WM_DELETE_WINDOW", false); w_root = DefaultRootWindow(d_display); shints.x = 0; shints.y = 0; shints.flags = 0; pos = (XWMGeometry(d_display, DefaultScreen(d_display), position, NULL, 0, &shints, &shints.x, &shints.y, &shints.width, &shints.height, &shints.win_gravity) & (XValue | YValue)) == 1; shints.min_width = winsize; shints.min_height = winsize; shints.max_width = winsize; shints.max_height = winsize; shints.base_width = winsize; shints.base_height = winsize; shints.flags = PMinSize | PMaxSize | PBaseSize; createWin(&w_main, shints.x, shints.y); if (wmaker || astep || pos) shints.flags |= USPosition; if (wmaker) { wmhints.initial_state = WithdrawnState; wmhints.flags = WindowGroupHint | StateHint | IconWindowHint; createWin(&w_icon, shints.x, shints.y); w_activewin = w_icon; wmhints.icon_window = w_icon; } else { wmhints.initial_state = NormalState; wmhints.flags = WindowGroupHint | StateHint; w_activewin = w_main; } wmhints.window_group = w_main; XSetWMHints(d_display, w_main, &wmhints); XSetWMNormalHints(d_display, w_main, &shints); XSetCommand(d_display, w_main, argv, argc); XStoreName(d_display, w_main, NAME); XSetIconName(d_display, w_main, NAME); XSetWMProtocols(d_display, w_activewin, &deleteWin, 1); } void freeXWin() { XDestroyWindow(d_display, w_main); if (wmaker) XDestroyWindow(d_display, w_icon); XCloseDisplay(d_display); } void createWin(Window *win, int x, int y) { XClassHint classHint; *win = XCreateSimpleWindow(d_display, w_root, x, y, winsize, winsize, 0, 0, 0); classHint.res_name = NAME; classHint.res_class = CLASS; XSetClassHint(d_display, *win, &classHint); } unsigned long mixColor(char *colorname1, int prop1, char *colorname2, int prop2) { XColor color, color1, color2; XWindowAttributes winattr; XGetWindowAttributes(d_display, w_root, &winattr); XParseColor(d_display, winattr.colormap, colorname1, &color1); XParseColor(d_display, winattr.colormap, colorname2, &color2); color.pixel = 0; color.red = (color1.red * prop1 + color2.red * prop2) / (prop1 + prop2); color.green = (color1.green * prop1 + color2.green * prop2) / (prop1 + prop2); color.blue = (color1.blue * prop1 + color2.blue * prop2) / (prop1 + prop2); color.flags = DoRed | DoGreen | DoBlue; XAllocColor(d_display, winattr.colormap, &color); return color.pixel; } void scanArgs(int argc, char **argv) { int i; for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { usage(); exit(0); } if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) { fprintf(stderr, "%s version %s\n", NAME, VERSION); exit(0); } if (strcmp(argv[i], "-w") == 0) wmaker = !wmaker; if (strcmp(argv[i], "-s") == 0) ushape = !ushape; if (strcmp(argv[i], "-a") == 0) astep = !astep; if (strcmp(argv[i], "-novol") == 0) no_volume_display = 1; // OSS Device selection (multiple allowed) if (strcmp(argv[i], "-o") == 0 || strcmp(argv[i], "--oss") == 0) { force_oss = true; if (i + 1 < argc && argv[i+1][0] != '-') { if (g_oss_dev_count < MAX_OSS_DEVS) { g_oss_dev_list[g_oss_dev_count++] = argv[++i]; } } else { // -o without argument implies default /dev/mixer if (g_oss_dev_count < MAX_OSS_DEVS) { g_oss_dev_list[g_oss_dev_count++] = "/dev/mixer"; } } continue; } if (strcmp(argv[i], "-d") == 0) { if (i < argc - 1) { i++; if (strlen(argv[i]) > 255) { fprintf(stderr, "%s: Illegal device name.\n", NAME); exit(1); } snprintf(card, sizeof(card), "%s", argv[i]); smixer_options.device = card; } continue; } if (strcmp(argv[i], "-l") == 0) { if (i < argc - 1) { i++; if (strlen(argv[i]) > 255) { fprintf(stderr, "%s: Illegal color name.\n", NAME); exit(1); } snprintf(ledcolor, sizeof(ledcolor), "%s", argv[i]); } continue; } if (strcmp(argv[i], "-b") == 0) { if (i < argc - 1) { i++; if (strlen(argv[i]) > 255) { fprintf(stderr, "%s: Illegal color name.\n", NAME); exit(1); } snprintf(backcolor, sizeof(backcolor), "%s", argv[i]); } continue; } if (strcmp(argv[i], "-position") == 0) { if (i < argc - 1) { i++; if (strlen(argv[i]) > 255) { fprintf(stderr, "%s: Illegal position.\n", NAME); exit(1); } snprintf(position, sizeof(position), "%s", argv[i]); } continue; } if (strcmp(argv[i], "-display") == 0) { if (i < argc - 1) { i++; if (strlen(argv[i]) > 255) { fprintf(stderr, "%s: Illegal display name.\n", NAME); exit(1); } snprintf(display, sizeof(display), "%s", argv[i]); } continue; } } } void checkVol(bool forced) { int nl = Mixer_read_left(curchannel); int nr = Mixer_read_right(curchannel); if (forced) { curleft = nl; curright = nr; update(); repaint(); } else { if (nl != curleft || nr != curright) { if (nl != curleft) { curleft = nl; if (selems[curchannel]->stereo) drawStereo(true); else drawMono(); } if (nr != curright) { curright = nr; if (selems[curchannel]->stereo) drawStereo(false); else drawMono(); } if (!no_volume_display) drawVolLevel(); repaint(); } } } void pressEvent(XButtonEvent *xev) { int inc, x, y, v; if (xev->button == Button4 || xev->button == Button5) { if (xev->button == Button4) inc = 4; else inc = -4; Mixer_set_left(curchannel, CLAMP(Mixer_read_left(curchannel) + inc, 0, 100)); Mixer_set_right(curchannel, CLAMP(Mixer_read_right(curchannel) + inc, 0, 100)); checkVol(false); return; } x = xev->x - (winsize / 2 - 32); y = xev->y - (winsize / 2 - 32); if (x >= 5 && y >= 47 && x <= 17 && y <= 57) { curchannel--; if (curchannel < 0) curchannel = mix->devices_no -1; btnstate |= BTNPREV; rpttimer = 0; drawBtns(BTNPREV); checkVol(true); return; } if (x >= 18 && y >= 47 && x <= 30 && y <= 57) { curchannel++; if (curchannel >= mix->devices_no) curchannel = 0; btnstate|=BTNNEXT; rpttimer = 0; drawBtns(BTNNEXT); checkVol(true); return; } if (x >= 37 && x <= 56 && y >= 8 && y <= 56) { v = ((60 - y) * 100) / (2 * 25); dragging = true; if (x <= 50) Mixer_set_left(curchannel, v); if (x >= 45) Mixer_set_right(curchannel, v); checkVol(false); return; } if (x >= 5 && y >= 21 && x <= 30 && y <= 42) { drawText(selems[curchannel]->name); return; } } void releaseEvent() { dragging = false; btnstate &= ~(BTNPREV | BTNNEXT); drawBtns(BTNPREV | BTNNEXT); repaint(); } void motionEvent(XMotionEvent *xev) { int x = xev->x - (winsize / 2 - 32); int y = xev->y - (winsize / 2 - 32); if (x >= 37 && x <= 56 && y >= 8 && dragging) { int v = ((60 - y) * 100) / (2 * 25); if (v < 0) v = 0; if (x <= 50) Mixer_set_left(curchannel, v); if (x >= 45) Mixer_set_right(curchannel, v); checkVol(false); } } void repaint() { XCopyArea(d_display, pm_disp, w_activewin, gc_gc, 0, 0, 64, 64, winsize / 2 - 32, winsize / 2 - 32); XEvent xev; while (XCheckTypedEvent(d_display, Expose, &xev)) { continue; } } void update() { drawText(selems[curchannel]->name); XCopyArea(d_display, pm_icon, pm_disp, gc_gc, selems[curchannel]->iconIndex * 26, 0, 26, 24, 5, 19); if (selems[curchannel]->stereo) { drawStereo(true); drawStereo(false); } else { drawMono(); } } void drawText(char *text) { char *p = text; char p2; int i; for (i = 0; i < 4; i++, p++) { p2 = toupper(*p); if (p2 >= 'A' && p2 <= 'Z') { XCopyArea(d_display, pm_chars, pm_disp, gc_gc, 6 * ((int)p2 - 65), 0, 6, 9, 5 + (i * 6), 5); } else if (p2 >= '0' && p2 <= '9') { XCopyArea(d_display, pm_digits, pm_disp, gc_gc, 6 * ((int)p2 - 48), 0, 6, 9, 5 + (i * 6), 5); } else { if (p2 == '\0') p--; XCopyArea(d_display, pm_digits, pm_disp, gc_gc, 60, 0, 6, 9, 5 + (i * 6), 5); } } if (!no_volume_display) text_counter = 10; } void drawVolLevel() { int i; int digits[4]; int vol = (Mixer_read_left(curchannel) + Mixer_read_right(curchannel)) / 2; digits[0] = (vol/100) ? 1 : 10; digits[1] = (vol/10) == 10 ? 0 : (vol/10); digits[2] = vol%10; digits[3] = 10; for (i = 0; i < 4; i++) { XCopyArea(d_display, pm_digits, pm_disp, gc_gc, 6*digits[i], 0, 6, 9, 5+(i*6), 5); } } void drawStereo(bool left) { int i; short pos = left? 37 : 48; XSetForeground(d_display, gc_gc, color[0]); XFillRectangle(d_display, pm_disp, gc_gc, 46, 7, 2, 49); XSetForeground(d_display, gc_gc, color[1]); for (i = 0; i < 25; i++) { if (i == ((left ? curleft : curright) * 25) / 100) XSetForeground(d_display, gc_gc, color[3]); XFillRectangle(d_display, pm_disp, gc_gc, pos, 55 - 2 * i, 9, 1); } } void drawMono() { int i; XSetForeground(d_display, gc_gc, color[1]); for (i = 0; i < 25; i++) { if (i == (curright * 25) / 100) XSetForeground(d_display, gc_gc, color[3]); XFillRectangle(d_display, pm_disp, gc_gc, 37, 55 - 2 * i, 20, 1); } } void drawBtns(int btns) { if (btns & BTNPREV) drawBtn(5, 47, 13, 11, (btnstate & BTNPREV)); if (btns & BTNNEXT) drawBtn(18, 47, 13, 11, (btnstate & BTNNEXT)); } void drawBtn(int x, int y, int w, int h, bool down) { if (!down) { XCopyArea(d_display, pm_main, pm_disp, gc_gc, x, y, w, h, x, y); } else { XCopyArea(d_display, pm_main, pm_disp, gc_gc, x, y, 1, h - 1, x + w - 1, y + 1); XCopyArea(d_display, pm_main, pm_disp, gc_gc, x + w - 1, y + 1, 1, h - 1, x, y); XCopyArea(d_display, pm_main, pm_disp, gc_gc, x, y, w - 1, 1, x + 1, y + h - 1); XCopyArea(d_display, pm_main, pm_disp, gc_gc, x + 1, y + h - 1, w - 1, 1, x, y); } } void usage() { printf("%s - An ALSA/OSS mixer designed for WindowMaker with ", NAME); fputs("\ scrollwheel support\n\ Copyright (C) 2015 Roman Dobosz \n\ Copyright (C) 2003 Damian Kramer \n\ Copyright (C) 1998 Sam Hawker \n\ This software comes with ABSOLUTELY NO WARRANTY\n\ This software is free software, and you are welcome to redistribute it\n\ under certain conditions\n\ See the README file for a more complete notice.\n\n", stdout); printf("usage:\n\n %s [options]\n\noptions:\n\n", NAME); fputs("\ -h | --help display this help screen\n\ -v | --version display the version\n\ -w use WithdrawnState (for WindowMaker)\n\ -s shaped window\n\ -a use smaller window (for AfterStep Wharf)\n\ -novol do not display volume level\n\ -o | --oss [dev] use OSS mixer (default /dev/mixer), can be specified multiple times\n\ -l led_color use the specified color for led display\n\ -b back_color use the specified color for backgrounds\n\ -d alsa_device use specified ALSA device (rather than 'default')\n\ -position position set window position (see X manual pages)\n\ -display display select target display (see X manual pages)\n\n", stdout); }