/* * ALSA command line mixer utility * Copyright (c) 1999-2000 by Jaroslav Kysela * * Modifications to show parseable output by: * Mario Izquierdo (mariodebian) * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include "tmixer-alsa.h" #define LEVEL_BASIC (1<<0) #define LEVEL_INACTIVE (1<<1) #define LEVEL_ID (1<<2) static int quiet = 0; static int debugflag = 0; static int no_check = 0; static int smixer_level = 0; static int ignore_error = 0; static struct snd_mixer_selem_regopt smixer_options; static char card[64] = "default"; static void error(const char *fmt,...) { va_list va; va_start(va, fmt); fprintf(stderr, "tmixer-alsa: "); vfprintf(stderr, fmt, va); fprintf(stderr, "\n"); va_end(va); } static int help(void) { printf("Usage: tmixer-alsa [command]\n"); printf("\nAvailable options:\n"); printf(" -h,--help this help\n"); printf(" -c,--card N select the card\n"); printf(" -D,--device N select the device, default '%s'\n", card); printf(" -d,--debug debug mode\n"); printf(" -n,--nocheck do not perform range checking\n"); printf(" -v,--version print version of this program\n"); printf(" -q,--quiet be quiet\n"); printf(" -i,--inactive show also inactive controls\n"); printf(" -a,--abstract L select abstraction level (none or basic)\n"); printf(" -s,--stdin Read and execute commands from stdin sequentially\n"); printf("\nAvailable commands:\n"); printf(" scontrols show all mixer simple controls\n"); printf(" scontents show contents of all mixer simple controls (default command)\n"); printf(" sset sID P set contents for one mixer simple control\n"); printf(" sget sID get contents for one mixer simple control\n"); printf(" controls show all controls for given card\n"); printf(" contents show contents of all controls for given card\n"); printf(" cset cID P set control contents for one control\n"); printf(" cget cID get control contents for one control\n"); printf("\ntmixer-alsa output format:\n"); printf(" ChannelName, ChannelType [volume|switch] , Volume in %% , SwitchStatus [on|off] \n\n"); return 0; } static int info(void) { int err; snd_ctl_t *handle; snd_mixer_t *mhandle; snd_ctl_card_info_t *info; snd_ctl_elem_list_t *clist; snd_ctl_card_info_alloca(&info); snd_ctl_elem_list_alloca(&clist); if ((err = snd_ctl_open(&handle, card, 0)) < 0) { error("Control device %s open error: %s", card, snd_strerror(err)); return err; } if ((err = snd_ctl_card_info(handle, info)) < 0) { error("Control device %s hw info error: %s", card, snd_strerror(err)); return err; } printf("Card %s '%s'/'%s'\n", card, snd_ctl_card_info_get_id(info), snd_ctl_card_info_get_longname(info)); printf(" Mixer name : '%s'\n", snd_ctl_card_info_get_mixername(info)); printf(" Components : '%s'\n", snd_ctl_card_info_get_components(info)); if ((err = snd_ctl_elem_list(handle, clist)) < 0) { error("snd_ctl_elem_list failure: %s", snd_strerror(err)); } else { printf(" Controls : %i\n", snd_ctl_elem_list_get_count(clist)); } snd_ctl_close(handle); if ((err = snd_mixer_open(&mhandle, 0)) < 0) { error("Mixer open error: %s", snd_strerror(err)); return err; } if (smixer_level == 0 && (err = snd_mixer_attach(mhandle, card)) < 0) { error("Mixer attach %s error: %s", card, snd_strerror(err)); snd_mixer_close(mhandle); return err; } if ((err = snd_mixer_selem_register(mhandle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) { error("Mixer register error: %s", snd_strerror(err)); snd_mixer_close(mhandle); return err; } err = snd_mixer_load(mhandle); if (err < 0) { error("Mixer load %s error: %s", card, snd_strerror(err)); snd_mixer_close(mhandle); return err; } printf(" Simple ctrls : %i\n", snd_mixer_get_count(mhandle)); snd_mixer_close(mhandle); return 0; } static const char *control_iface(snd_ctl_elem_id_t *id) { return snd_ctl_elem_iface_name(snd_ctl_elem_id_get_interface(id)); } static const char *control_type(snd_ctl_elem_info_t *info) { return snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)); } static const char *control_access(snd_ctl_elem_info_t *info) { static char result[10]; char *res = result; *res++ = snd_ctl_elem_info_is_readable(info) ? 'r' : '-'; *res++ = snd_ctl_elem_info_is_writable(info) ? 'w' : '-'; *res++ = snd_ctl_elem_info_is_inactive(info) ? 'i' : '-'; *res++ = snd_ctl_elem_info_is_volatile(info) ? 'v' : '-'; *res++ = snd_ctl_elem_info_is_locked(info) ? 'l' : '-'; *res++ = snd_ctl_elem_info_is_tlv_readable(info) ? 'R' : '-'; *res++ = snd_ctl_elem_info_is_tlv_writable(info) ? 'W' : '-'; *res++ = snd_ctl_elem_info_is_tlv_commandable(info) ? 'C' : '-'; *res++ = '\0'; return result; } #define check_range(val, min, max) \ (no_check ? (val) : ((val < min) ? (min) : (val > max) ? (max) : (val))) #if 0 static int convert_range(int val, int omin, int omax, int nmin, int nmax) { int orange = omax - omin, nrange = nmax - nmin; if (orange == 0) return 0; return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / ((double)orange + (double)nmin)); } #endif #if 0 static int convert_db_range(int val, int omin, int omax, int nmin, int nmax) { int orange = omax - omin, nrange = nmax - nmin; if (orange == 0) return 0; return rint((((double)nrange * ((double)val - (double)omin)) + ((double)orange / 2.0)) / (double)orange + (double)nmin); } #endif /* Fuction to convert from volume to percentage. val = volume */ static int convert_prange(int val, int min, int max) { int range = max - min; int tmp; if (range == 0) return 0; val -= min; tmp = rint((double)val/(double)range * 100); return tmp; } /* Function to convert from percentage to volume. val = percentage */ #define convert_prange1(val, min, max) \ ceil((val) * ((max) - (min)) * 0.01 + (min)) static const char *get_percent(int val, int min, int max) { static char str[32]; int p; p = convert_prange(val, min, max); /*sprintf(str, "%i [%i%%]", val, p);*/ sprintf(str, "%i", p); return str; } #if 0 static const char *get_percent1(int val, int min, int max, int min_dB, int max_dB) { static char str[32]; int p, db; p = convert_prange(val, min, max); db = convert_db_range(val, min, max, min_dB, max_dB); sprintf(str, "%i [%i%%] [%i.%02idB]", val, p, db / 100, abs(db % 100)); return str; } #endif static long get_integer(char **ptr, long min, long max) { long val = min; char *p = *ptr, *s; long int ret; if (*p == ':') p++; if (*p == '\0' || (!isdigit(*p) && *p != '-')) goto out; s = p; val = strtol(s, &p, 10); if (*p == '.') { p++; ret=strtol(p, &p, 10); } if (*p == '%') { val = (long)convert_prange1(strtod(s, NULL), min, max); p++; } val = check_range(val, min, max); if (*p == ',') p++; out: *ptr = p; return val; } static long get_integer64(char **ptr, long long min, long long max) { long long val = min; char *p = *ptr, *s; long int ret; if (*p == ':') p++; if (*p == '\0' || (!isdigit(*p) && *p != '-')) goto out; s = p; val = strtol(s, &p, 10); if (*p == '.') { p++; ret = strtol(p, &p, 10); } if (*p == '%') { val = (long long)convert_prange1(strtod(s, NULL), min, max); p++; } val = check_range(val, min, max); if (*p == ',') p++; out: *ptr = p; return val; } struct volume_ops { int (*get_range)(snd_mixer_elem_t *elem, long *min, long *max); int (*get)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c, long *value); int (*set)(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c, long value); }; enum { VOL_RAW, VOL_DB }; struct volume_ops_set { int (*has_volume)(snd_mixer_elem_t *elem); struct volume_ops v[2]; }; static int set_playback_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c, long value) { return snd_mixer_selem_set_playback_dB(elem, c, value, 0); } static int set_capture_dB(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t c, long value) { return snd_mixer_selem_set_capture_dB(elem, c, value, 0); } static const struct volume_ops_set vol_ops[2] = { { .has_volume = snd_mixer_selem_has_playback_volume, .v = {{ snd_mixer_selem_get_playback_volume_range, snd_mixer_selem_get_playback_volume, snd_mixer_selem_set_playback_volume }, { snd_mixer_selem_get_playback_dB_range, snd_mixer_selem_get_playback_dB, set_playback_dB }}, }, { .has_volume = snd_mixer_selem_has_capture_volume, .v = {{ snd_mixer_selem_get_capture_volume_range, snd_mixer_selem_get_capture_volume, snd_mixer_selem_set_capture_volume }, { snd_mixer_selem_get_capture_dB_range, snd_mixer_selem_get_capture_dB, set_capture_dB }}, }, }; static int set_volume_simple(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t chn, char **ptr, int dir) { long val, orig, pmin, pmax; char *p = *ptr, *s; int invalid = 0, err = 0, vol_type = VOL_RAW; long int ret; if (! vol_ops[dir].has_volume(elem)) invalid = 1; if (*p == ':') p++; if (*p == '\0' || (!isdigit(*p) && *p != '-')) goto skip; if (! invalid && vol_ops[dir].v[VOL_RAW].get_range(elem, &pmin, &pmax) < 0) invalid = 1; s = p; val = strtol(s, &p, 10); if (*p == '.') { p++; ret = strtol(p, &p, 10); } if (*p == '%') { if (! invalid) val = (long)convert_prange1(strtod(s, NULL), pmin, pmax); p++; /* FIXME tmixer-alsa conversion lose original % because of 0-39 limits */ /*printf("DEBUG: pmin=%li pmax=%li val=%li\n",pmin,pmax,val);*/ } else if (p[0] == 'd' && p[1] == 'B') { if (! invalid) { val = (long)(strtod(s, NULL) * 100.0); vol_type = VOL_DB; if (vol_ops[dir].v[vol_type].get_range(elem, &pmin, &pmax) < 0) invalid = 1; } p += 2; } if (*p == '+' || *p == '-') { if (! invalid) { if (vol_ops[dir].v[vol_type].get(elem, chn, &orig) < 0) invalid = 1; if (*p == '+') val = orig + val; else val = orig - val; } p++; } if (! invalid) { val = check_range(val, pmin, pmax); err = vol_ops[dir].v[vol_type].set(elem, chn, val); } skip: if (*p == ',') p++; *ptr = p; return err ? err : (invalid ? -ENOENT : 0); } static int get_bool_simple(char **ptr, char *str, int invert, int orig) { if (**ptr == ':') (*ptr)++; if (!strncasecmp(*ptr, str, strlen(str))) { orig = 1 ^ (invert ? 1 : 0); while (**ptr != '\0' && **ptr != ',' && **ptr != ':') (*ptr)++; } if (**ptr == ',' || **ptr == ':') (*ptr)++; return orig; } static int simple_skip_word(char **ptr, char *str) { char *xptr = *ptr; if (*xptr == ':') xptr++; if (!strncasecmp(xptr, str, strlen(str))) { while (*xptr != '\0' && *xptr != ',' && *xptr != ':') xptr++; if (*xptr == ',' || *xptr == ':') xptr++; *ptr = xptr; return 1; } return 0; } static void show_control_id(snd_ctl_elem_id_t *id) { unsigned int index, device, subdevice; printf("numid=%u,iface=%s,name='%s'", snd_ctl_elem_id_get_numid(id), control_iface(id), snd_ctl_elem_id_get_name(id)); index = snd_ctl_elem_id_get_index(id); device = snd_ctl_elem_id_get_device(id); subdevice = snd_ctl_elem_id_get_subdevice(id); if (index) printf(",index=%i", index); if (device) printf(",device=%i", device); if (subdevice) printf(",subdevice=%i", subdevice); } static void print_spaces(unsigned int spaces) { while (spaces-- > 0) putc(' ', stdout); } static void print_dB(long dB) { printf("%li.%02lidB", dB / 100, (dB < 0 ? -dB : dB) % 100); } static void decode_tlv(unsigned int spaces, unsigned int *tlv, unsigned int tlv_size) { unsigned int type = tlv[0]; unsigned int size; unsigned int idx = 0; if (tlv_size < 2 * sizeof(unsigned int)) { printf("TLV size error!\n"); return; } print_spaces(spaces); printf("| "); type = tlv[idx++]; size = tlv[idx++]; tlv_size -= 2 * sizeof(unsigned int); if (size > tlv_size) { printf("TLV size error (%i, %i, %i)!\n", type, size, tlv_size); return; } switch (type) { case SND_CTL_TLVT_CONTAINER: size += sizeof(unsigned int) -1; size /= sizeof(unsigned int); while (idx < size) { if (tlv[idx+1] > (size - idx) * sizeof(unsigned int)) { printf("TLV size error in compound!\n"); return; } decode_tlv(spaces + 2, tlv + idx, tlv[idx+1]); idx += 2 + (tlv[1] + sizeof(unsigned int) - 1) / sizeof(unsigned int); } break; case SND_CTL_TLVT_DB_SCALE: printf("dBscale-"); if (size != 2 * sizeof(unsigned int)) { while (size > 0) { printf("0x%08x,", tlv[idx++]); size -= sizeof(unsigned int); } } else { printf("min="); print_dB((int)tlv[2]); printf(",step="); print_dB(tlv[3] & 0xffff); printf(",mute=%i", (tlv[3] >> 16) & 1); } break; #ifdef SND_CTL_TLVT_DB_LINEAR case SND_CTL_TLVT_DB_LINEAR: printf("dBlinear-"); if (size != 2 * sizeof(unsigned int)) { while (size > 0) { printf("0x%08x,", tlv[idx++]); size -= sizeof(unsigned int); } } else { printf("min="); print_dB(tlv[2]); printf(",max="); print_dB(tlv[3]); } break; #endif #ifdef SND_CTL_TLVT_DB_RANGE case SND_CTL_TLVT_DB_RANGE: printf("dBrange-\n"); if ((size / (6 * sizeof(unsigned int))) != 0) { while (size > 0) { printf("0x%08x,", tlv[idx++]); size -= sizeof(unsigned int); } break; } idx = 0; while (idx < size) { print_spaces(spaces + 2); printf("rangemin=%i,", tlv[0]); printf(",rangemax=%i\n", tlv[1]); decode_tlv(spaces + 4, tlv + 2, 6 * sizeof(unsigned int)); idx += 6 * sizeof(unsigned int); } break; #endif default: printf("unk-%i-", type); while (size > 0) { printf("0x%08x,", tlv[idx++]); size -= sizeof(unsigned int); } break; } putc('\n', stdout); } static int show_control(const char *space, snd_hctl_elem_t *elem, int level) { int err; unsigned int item, idx, count, *tlv; snd_ctl_elem_type_t type; snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *control; snd_aes_iec958_t iec958; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_info_alloca(&info); snd_ctl_elem_value_alloca(&control); if ((err = snd_hctl_elem_info(elem, info)) < 0) { error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err)); return err; } if (level & LEVEL_ID) { snd_hctl_elem_get_id(elem, id); show_control_id(id); printf("\n"); } count = snd_ctl_elem_info_get_count(info); type = snd_ctl_elem_info_get_type(info); printf("%s; type=%s,access=%s,values=%i", space, control_type(info), control_access(info), count); switch (type) { case SND_CTL_ELEM_TYPE_INTEGER: printf(",min=%li,max=%li,step=%li\n", snd_ctl_elem_info_get_min(info), snd_ctl_elem_info_get_max(info), snd_ctl_elem_info_get_step(info)); break; case SND_CTL_ELEM_TYPE_INTEGER64: printf(",min=%Li,max=%Li,step=%Li\n", snd_ctl_elem_info_get_min64(info), snd_ctl_elem_info_get_max64(info), snd_ctl_elem_info_get_step64(info)); break; case SND_CTL_ELEM_TYPE_ENUMERATED: { unsigned int items = snd_ctl_elem_info_get_items(info); printf(",items=%u\n", items); for (item = 0; item < items; item++) { snd_ctl_elem_info_set_item(info, item); if ((err = snd_hctl_elem_info(elem, info)) < 0) { error("Control %s element info error: %s\n", card, snd_strerror(err)); return err; } printf("%s; Item #%u '%s'\n", space, item, snd_ctl_elem_info_get_item_name(info)); } break; } default: printf("\n"); break; } if (level & LEVEL_BASIC) { if ((err = snd_hctl_elem_read(elem, control)) < 0) { error("Control %s element read error: %s\n", card, snd_strerror(err)); return err; } printf("%s: values=", space); for (idx = 0; idx < count; idx++) { if (idx > 0) printf(","); switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: printf("%s", snd_ctl_elem_value_get_boolean(control, idx) ? "on" : "off"); break; case SND_CTL_ELEM_TYPE_INTEGER: printf("%li", snd_ctl_elem_value_get_integer(control, idx)); break; case SND_CTL_ELEM_TYPE_INTEGER64: printf("%Li", snd_ctl_elem_value_get_integer64(control, idx)); break; case SND_CTL_ELEM_TYPE_ENUMERATED: printf("%u", snd_ctl_elem_value_get_enumerated(control, idx)); break; case SND_CTL_ELEM_TYPE_BYTES: printf("0x%02x", snd_ctl_elem_value_get_byte(control, idx)); break; case SND_CTL_ELEM_TYPE_IEC958: snd_ctl_elem_value_get_iec958(control, &iec958); printf("[AES0=0x%02x AES1=0x%02x AES2=0x%02x AES3=0x%02x]", iec958.status[0], iec958.status[1], iec958.status[2], iec958.status[3]); break; default: printf("?"); break; } } printf("\n"); if (!snd_ctl_elem_info_is_tlv_readable(info)) goto __skip_tlv; tlv = malloc(4096); if ((err = snd_hctl_elem_tlv_read(elem, tlv, 4096)) < 0) { error("Control %s element TLV read error: %s\n", card, snd_strerror(err)); free(tlv); return err; } decode_tlv(strlen(space), tlv, 4096); free(tlv); } __skip_tlv: return 0; } static int controls(int level) { int err; snd_hctl_t *handle; snd_hctl_elem_t *elem; snd_ctl_elem_id_t *id; snd_ctl_elem_info_t *info; snd_ctl_elem_id_alloca(&id); snd_ctl_elem_info_alloca(&info); if ((err = snd_hctl_open(&handle, card, 0)) < 0) { error("Control %s open error: %s", card, snd_strerror(err)); return err; } if ((err = snd_hctl_load(handle)) < 0) { error("Control %s local error: %s\n", card, snd_strerror(err)); return err; } for (elem = snd_hctl_first_elem(handle); elem; elem = snd_hctl_elem_next(elem)) { if ((err = snd_hctl_elem_info(elem, info)) < 0) { error("Control %s snd_hctl_elem_info error: %s\n", card, snd_strerror(err)); return err; } if (!(level & LEVEL_INACTIVE) && snd_ctl_elem_info_is_inactive(info)) continue; snd_hctl_elem_get_id(elem, id); show_control_id(id); printf("\n"); if (level & LEVEL_BASIC) show_control(" ", elem, 1); } snd_hctl_close(handle); return 0; } static int show_selem(snd_mixer_t *handle, snd_mixer_selem_id_t *id, const char *space, int level) { snd_mixer_selem_channel_id_t chn; long pmin = 0, pmax = 0; long cmin = 0, cmax = 0; // Allow control capture channels for rtp, mic..etc long pvol, cvol; //long pvol; int psw, csw; //int psw; int pmono, cmono, mono_ok = 0; /*long db;*/ snd_mixer_elem_t *elem; elem = snd_mixer_find_selem(handle, id); if (!elem) { error("Mixer %s simple element not found", card); return -ENOENT; } /* don't show enumerates */ if ( snd_mixer_selem_is_enumerated(elem) ) { /*printf("DEBUG ENUMERATE not show '%s'\n", snd_mixer_selem_id_get_name(id) ); printf(" enumerated %d\n", snd_mixer_selem_is_enumerated(elem));*/ return 0; } //if ( !snd_mixer_selem_has_playback_volume(elem) && snd_mixer_selem_has_capture_volume(elem) ) { /* don't show only capture channels */ // return 0; //} if ( level & LEVEL_BASIC ) { printf("%s,", snd_mixer_selem_id_get_name(id) ); /*printf("%sCapabilities:", space);*/ if( snd_mixer_selem_has_common_volume(elem) || snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem) ) { printf("volume|"); } if ( snd_mixer_selem_has_common_switch(elem) || snd_mixer_selem_has_playback_switch(elem) || snd_mixer_selem_has_capture_switch(elem) ) { printf("switch|"); } printf(","); if (snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) { /*printf("%sLimits:", space);*/ if (snd_mixer_selem_has_common_volume(elem)) { snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax); snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax); /*printf(" %li - %li", pmin, pmax);*/ } else { if (snd_mixer_selem_has_playback_volume(elem)) { snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax); /*printf(" Playback %li - %li", pmin, pmax);*/ } if (snd_mixer_selem_has_capture_volume(elem)) { snd_mixer_selem_get_capture_volume_range(elem, &cmin, &cmax); /*printf(" Capture %li - %li", cmin, cmax);*/ } } /*printf("\n");*/ } pmono = snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_MONO) && (snd_mixer_selem_is_playback_mono(elem) || (!snd_mixer_selem_has_playback_volume(elem) && !snd_mixer_selem_has_playback_switch(elem))); cmono = snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO) && (snd_mixer_selem_is_capture_mono(elem) || (!snd_mixer_selem_has_capture_volume(elem) && !snd_mixer_selem_has_capture_switch(elem))); if (pmono && snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_MONO)) { int title = 0; if (!mono_ok) { /*printf("%s%s:", space, "Mono");*/ mono_ok = 1; } if (!snd_mixer_selem_has_common_volume(elem)) { if (snd_mixer_selem_has_playback_volume(elem)) { /*printf("Playback");*/ title = 1; snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_MONO, &pvol); printf("%s", get_percent(pvol, pmin, pmax)); /*if (!snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_MONO, &db)) { printf(" ["); print_dB(db); printf("]"); }*/ } } if (!snd_mixer_selem_has_common_switch(elem)) { if (snd_mixer_selem_has_playback_switch(elem)) { /*if (!title) printf(" Playback");*/ snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &psw); /*printf(" [%s]", psw ? "on" : "off");*/ printf(",%s", psw ? "on" : "off"); } } } if (pmono && snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_MONO)) { int title = 0; if (!mono_ok) { /*printf("%s%s:", space, "Mono");*/ mono_ok = 1; } if (!snd_mixer_selem_has_common_volume(elem)) { if (snd_mixer_selem_has_capture_volume(elem)) { /*printf("Playback");*/ title = 1; snd_mixer_selem_get_capture_volume(elem, SND_MIXER_SCHN_MONO, &cvol); printf("%s", get_percent(cvol, cmin, cmax)); /*if (!snd_mixer_selem_get_playback_dB(elem, SND_MIXER_SCHN_MONO, &db)) { printf(" ["); print_dB(db); printf("]"); }*/ } } if (!snd_mixer_selem_has_common_switch(elem)) { if (snd_mixer_selem_has_capture_switch(elem)) { /*if (!title) printf(" Capture");*/ snd_mixer_selem_get_capture_switch(elem, SND_MIXER_SCHN_MONO, &csw); /*printf(" [%s]", psw ? "on" : "off");*/ printf(",%s", csw ? "on" : "off"); } } } /*if (pmono || cmono) printf("\n");*/ if (!pmono || !cmono) { for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { if ((pmono || !snd_mixer_selem_has_playback_channel(elem, chn)) && (cmono || !snd_mixer_selem_has_capture_channel(elem, chn))) continue; int printed=0; if (snd_mixer_selem_has_playback_channel(elem, chn)) { /*printf("==> %s%s:", space, snd_mixer_selem_channel_name(chn));*/ if (!pmono && !cmono && snd_mixer_selem_has_common_volume(elem)) { snd_mixer_selem_get_playback_volume(elem, chn, &pvol); printf("%s,", get_percent(pvol, pmin, pmax)); printed=1; } if (!pmono && !cmono && snd_mixer_selem_has_common_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &psw); /*printf(" [%s]", psw ? "on" : "off");*/ printf("%s", psw ? "on" : "off"); printed=1; } if (!pmono && snd_mixer_selem_has_playback_channel(elem, chn)) { int title = 0; if (!snd_mixer_selem_has_common_volume(elem)) { if (snd_mixer_selem_has_playback_volume(elem)) { /*printf(" Playback");*/ title = 1; snd_mixer_selem_get_playback_volume(elem, chn, &pvol); printf("%s,", get_percent(pvol, pmin, pmax)); printed=1; } } if (!snd_mixer_selem_has_common_switch(elem)) { if (snd_mixer_selem_has_playback_switch(elem)) { /*if (!title) printf(" Playback");*/ snd_mixer_selem_get_playback_switch(elem, chn, &psw); /*printf(" [%s]", psw ? "on" : "off");*/ printf("%s", psw ? "on" : "off"); printed=1; } } } } if (snd_mixer_selem_has_capture_channel(elem, chn)) { /*printf("==> %s%s:", space, snd_mixer_selem_channel_name(chn));*/ if (!pmono && !cmono && snd_mixer_selem_has_common_volume(elem)) { snd_mixer_selem_get_capture_volume(elem, chn, &cvol); printf("%s,", get_percent(cvol, cmin, cmax)); printed=1; } if (!pmono && !cmono && snd_mixer_selem_has_common_switch(elem)) { snd_mixer_selem_get_capture_switch(elem, chn, &csw); /*printf(" [%s]", psw ? "on" : "off");*/ printf("%s", csw ? "on" : "off"); printed=1; } if (!pmono && snd_mixer_selem_has_capture_channel(elem, chn)) { int title = 0; if (!snd_mixer_selem_has_common_volume(elem)) { if (snd_mixer_selem_has_capture_volume(elem)) { /*printf(" Playback");*/ title = 1; snd_mixer_selem_get_capture_volume(elem, chn, &cvol); printf("%s,", get_percent(cvol, cmin, cmax)); printed=1; } } if (!snd_mixer_selem_has_common_switch(elem)) { if (snd_mixer_selem_has_capture_switch(elem)) { /*if (!title) printf(" Capture");*/ snd_mixer_selem_get_capture_switch(elem, chn, &csw); /*printf(" [%s]", psw ? "on" : "off");*/ printf("%s", csw ? "on" : "off"); printed=1; } } } } /* only want one channel */ if( printed > 0) { break; } /*printf("\n");*/ } } printf("\n"); } else { if ( strlen(snd_mixer_selem_id_get_name(id)) > 0 ) { printf("%s|", snd_mixer_selem_id_get_name(id) ); } } return 0; } static int selems(int level) { int err; snd_mixer_t *handle; snd_mixer_selem_id_t *sid; snd_mixer_elem_t *elem; snd_mixer_selem_id_alloca(&sid); if ((err = snd_mixer_open(&handle, 0)) < 0) { error("Mixer %s open error: %s", card, snd_strerror(err)); return err; } if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) { error("Mixer attach %s error: %s", card, snd_strerror(err)); snd_mixer_close(handle); return err; } if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) { error("Mixer register error: %s", snd_strerror(err)); snd_mixer_close(handle); return err; } err = snd_mixer_load(handle); if (err < 0) { error("Mixer %s load error: %s", card, snd_strerror(err)); snd_mixer_close(handle); return err; } for (elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next(elem)) { snd_mixer_selem_get_id(elem, sid); if (!(level & LEVEL_INACTIVE) && !snd_mixer_selem_is_active(elem)) continue; /*printf("Simple mixer control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));*/ show_selem(handle, sid, " ", level); } snd_mixer_close(handle); return 0; } static int parse_control_id(const char *str, snd_ctl_elem_id_t *id) { int c, size, numid; char *ptr; while (*str == ' ' || *str == '\t') str++; if (!(*str)) return -EINVAL; snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); /* default */ while (*str) { if (!strncasecmp(str, "numid=", 6)) { str += 6; numid = atoi(str); if (numid <= 0) { fprintf(stderr, "tmixer-alsa: Invalid numid %d\n", numid); return -EINVAL; } snd_ctl_elem_id_set_numid(id, atoi(str)); while (isdigit(*str)) str++; } else if (!strncasecmp(str, "iface=", 6)) { str += 6; if (!strncasecmp(str, "card", 4)) { snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_CARD); str += 4; } else if (!strncasecmp(str, "mixer", 5)) { snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); str += 5; } else if (!strncasecmp(str, "pcm", 3)) { snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); str += 3; } else if (!strncasecmp(str, "rawmidi", 7)) { snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_RAWMIDI); str += 7; } else if (!strncasecmp(str, "timer", 5)) { snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_TIMER); str += 5; } else if (!strncasecmp(str, "sequencer", 9)) { snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_SEQUENCER); str += 9; } else { return -EINVAL; } } else if (!strncasecmp(str, "name=", 5)) { char buf[64]; str += 5; ptr = buf; size = 0; if (*str == '\'' || *str == '\"') { c = *str++; while (*str && *str != c) { if (size < (int)sizeof(buf)) { *ptr++ = *str; size++; } str++; } if (*str == c) str++; } else { while (*str && *str != ',') { if (size < (int)sizeof(buf)) { *ptr++ = *str; size++; } str++; } *ptr = '\0'; } snd_ctl_elem_id_set_name(id, buf); } else if (!strncasecmp(str, "index=", 6)) { str += 6; snd_ctl_elem_id_set_index(id, atoi(str)); while (isdigit(*str)) str++; } else if (!strncasecmp(str, "device=", 7)) { str += 7; snd_ctl_elem_id_set_device(id, atoi(str)); while (isdigit(*str)) str++; } else if (!strncasecmp(str, "subdevice=", 10)) { str += 10; snd_ctl_elem_id_set_subdevice(id, atoi(str)); while (isdigit(*str)) str++; } if (*str == ',') { str++; } else { if (*str) return -EINVAL; } } return 0; } static int parse_simple_id(const char *str, snd_mixer_selem_id_t *sid) { int c, size; char buf[128]; char *ptr = buf; while (*str == ' ' || *str == '\t') str++; if (!(*str)) return -EINVAL; size = 1; /* for '\0' */ if (*str != '"' && *str != '\'') { while (*str && *str != ',') { if (size < (int)sizeof(buf)) { *ptr++ = *str; size++; } str++; } } else { c = *str++; while (*str && *str != c) { if (size < (int)sizeof(buf)) { *ptr++ = *str; size++; } str++; } if (*str == c) str++; } if (*str == '\0') { snd_mixer_selem_id_set_index(sid, 0); *ptr = 0; goto _set; } if (*str != ',') return -EINVAL; *ptr = 0; /* terminate the string */ str++; if (!isdigit(*str)) return -EINVAL; snd_mixer_selem_id_set_index(sid, atoi(str)); _set: snd_mixer_selem_id_set_name(sid, buf); return 0; } static int get_ctl_enum_item_index(snd_ctl_t *handle, snd_ctl_elem_info_t *info, char **ptrp) { char *ptr = *ptrp; int items, i, len; const char *name; items = snd_ctl_elem_info_get_items(info); if (items <= 0) return -1; for (i = 0; i < items; i++) { snd_ctl_elem_info_set_item(info, i); if (snd_ctl_elem_info(handle, info) < 0) return -1; name = snd_ctl_elem_info_get_item_name(info); len = strlen(name); if (! strncmp(name, ptr, len)) { if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') { ptr += len; *ptrp = ptr; return i; } } } return -1; } static int cset(int argc, char *argv[], int roflag, int keep_handle) { int err; static snd_ctl_t *handle = NULL; snd_ctl_elem_info_t *info; snd_ctl_elem_id_t *id; snd_ctl_elem_value_t *control; char *ptr; unsigned int idx, count; long tmp; snd_ctl_elem_type_t type; snd_ctl_elem_info_alloca(&info); snd_ctl_elem_id_alloca(&id); snd_ctl_elem_value_alloca(&control); if (argc < 1) { fprintf(stderr, "Specify a full control identifier: [[iface=,][name='name',][index=,][device=,][subdevice=]]|[numid=]\n"); return -EINVAL; } if (parse_control_id(argv[0], id)) { fprintf(stderr, "Wrong control identifier: %s\n", argv[0]); return -EINVAL; } if (debugflag) { printf("VERIFY ID: "); show_control_id(id); printf("\n"); } if (handle == NULL && (err = snd_ctl_open(&handle, card, 0)) < 0) { error("Control %s open error: %s\n", card, snd_strerror(err)); return err; } snd_ctl_elem_info_set_id(info, id); if ((err = snd_ctl_elem_info(handle, info)) < 0) { if (ignore_error) return 0; error("Cannot find the given element from control %s\n", card); if (! keep_handle) { snd_ctl_close(handle); handle = NULL; } return err; } snd_ctl_elem_info_get_id(info, id); /* FIXME: Remove it when hctl find works ok !!! */ type = snd_ctl_elem_info_get_type(info); count = snd_ctl_elem_info_get_count(info); snd_ctl_elem_value_set_id(control, id); if (!roflag) { ptr = argv[1]; for (idx = 0; idx < count && idx < 128 && ptr && *ptr; idx++) { switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: tmp = 0; if (!strncasecmp(ptr, "on", 2) || !strncasecmp(ptr, "up", 2)) { tmp = 1; ptr += 2; } else if (!strncasecmp(ptr, "yes", 3)) { tmp = 1; ptr += 3; } else if (!strncasecmp(ptr, "toggle", 6)) { tmp = snd_ctl_elem_value_get_boolean(control, idx); tmp = tmp > 0 ? 0 : 1; ptr += 6; } else if (isdigit(*ptr)) { tmp = atoi(ptr) > 0 ? 1 : 0; while (isdigit(*ptr)) ptr++; } else { while (*ptr && *ptr != ',') ptr++; } snd_ctl_elem_value_set_boolean(control, idx, tmp); break; case SND_CTL_ELEM_TYPE_INTEGER: tmp = get_integer(&ptr, snd_ctl_elem_info_get_min(info), snd_ctl_elem_info_get_max(info)); snd_ctl_elem_value_set_integer(control, idx, tmp); break; case SND_CTL_ELEM_TYPE_INTEGER64: tmp = get_integer64(&ptr, snd_ctl_elem_info_get_min64(info), snd_ctl_elem_info_get_max64(info)); snd_ctl_elem_value_set_integer64(control, idx, tmp); break; case SND_CTL_ELEM_TYPE_ENUMERATED: tmp = get_ctl_enum_item_index(handle, info, &ptr); if (tmp < 0) tmp = get_integer(&ptr, 0, snd_ctl_elem_info_get_items(info) - 1); snd_ctl_elem_value_set_enumerated(control, idx, tmp); break; case SND_CTL_ELEM_TYPE_BYTES: tmp = get_integer(&ptr, 0, 255); snd_ctl_elem_value_set_byte(control, idx, tmp); break; default: break; } if (!strchr(argv[1], ',')) ptr = argv[1]; else if (*ptr == ',') ptr++; } if ((err = snd_ctl_elem_write(handle, control)) < 0) { if (!ignore_error) error("Control %s element write error: %s\n", card, snd_strerror(err)); if (!keep_handle) { snd_ctl_close(handle); handle = NULL; } return ignore_error ? 0 : err; } } if (! keep_handle) { snd_ctl_close(handle); handle = NULL; } if (!quiet) { snd_hctl_t *hctl; snd_hctl_elem_t *elem; if ((err = snd_hctl_open(&hctl, card, 0)) < 0) { error("Control %s open error: %s\n", card, snd_strerror(err)); return err; } if ((err = snd_hctl_load(hctl)) < 0) { error("Control %s load error: %s\n", card, snd_strerror(err)); return err; } elem = snd_hctl_find_elem(hctl, id); if (elem) show_control(" ", elem, LEVEL_BASIC | LEVEL_ID); else printf("Could not find the specified element\n"); snd_hctl_close(hctl); } return 0; } typedef struct channel_mask { char *name; unsigned int mask; } channel_mask_t; static const channel_mask_t chanmask[] = { {"frontleft", 1 << SND_MIXER_SCHN_FRONT_LEFT}, {"frontright", 1 << SND_MIXER_SCHN_FRONT_RIGHT}, {"frontcenter", 1 << SND_MIXER_SCHN_FRONT_CENTER}, {"front", ((1 << SND_MIXER_SCHN_FRONT_LEFT) | (1 << SND_MIXER_SCHN_FRONT_RIGHT))}, {"center", 1 << SND_MIXER_SCHN_FRONT_CENTER}, {"rearleft", 1 << SND_MIXER_SCHN_REAR_LEFT}, {"rearright", 1 << SND_MIXER_SCHN_REAR_RIGHT}, {"rear", ((1 << SND_MIXER_SCHN_REAR_LEFT) | (1 << SND_MIXER_SCHN_REAR_RIGHT))}, {"woofer", 1 << SND_MIXER_SCHN_WOOFER}, {NULL, 0} }; static unsigned int channels_mask(char **arg, unsigned int def) { const channel_mask_t *c; for (c = chanmask; c->name; c++) { if (strncasecmp(*arg, c->name, strlen(c->name)) == 0) { while (**arg != '\0' && **arg != ',' && **arg != ' ' && **arg != '\t') (*arg)++; if (**arg == ',' || **arg == ' ' || **arg == '\t') (*arg)++; return c->mask; } } return def; } static unsigned int dir_mask(char **arg, unsigned int def) { int findend = 0; if (strncasecmp(*arg, "playback", 8) == 0) def = findend = 1; else if (strncasecmp(*arg, "capture", 8) == 0) def = findend = 2; if (findend) { while (**arg != '\0' && **arg != ',' && **arg != ' ' && **arg != '\t') (*arg)++; if (**arg == ',' || **arg == ' ' || **arg == '\t') (*arg)++; } return def; } static int get_enum_item_index(snd_mixer_elem_t *elem, char **ptrp) { char *ptr = *ptrp; int items, i, len; char name[40]; items = snd_mixer_selem_get_enum_items(elem); if (items <= 0) return -1; for (i = 0; i < items; i++) { if (snd_mixer_selem_get_enum_item_name(elem, i, sizeof(name)-1, name) < 0) continue; len = strlen(name); if (! strncmp(name, ptr, len)) { if (! ptr[len] || ptr[len] == ',' || ptr[len] == '\n') { ptr += len; *ptrp = ptr; return i; } } } return -1; } static int sset_enum(snd_mixer_elem_t *elem, unsigned int argc, char **argv) { unsigned int idx, chn = 0; int check_flag = ignore_error ? 0 : -1; for (idx = 1; idx < argc; idx++) { char *ptr = argv[idx]; while (*ptr) { int ival = get_enum_item_index(elem, &ptr); if (ival < 0) return check_flag; if (snd_mixer_selem_set_enum_item(elem, chn, ival) >= 0) check_flag = 1; /* skip separators */ while (*ptr == ',' || isspace(*ptr)) ptr++; } } return check_flag; } static int sset_channels(snd_mixer_elem_t *elem, unsigned int argc, char **argv) { unsigned int channels = ~0U; unsigned int dir = 3, okflag = 3; unsigned int idx; snd_mixer_selem_channel_id_t chn; int check_flag = ignore_error ? 0 : -1; for (idx = 1; idx < argc; idx++) { char *ptr = argv[idx], *optr; /*printf("DEBUG: argc=%d, arvg[0]=%s, argv[1]=%s\n", argc, argv[0], argv[1]);*/ int multi, firstchn = 1; channels = channels_mask(&ptr, channels); if (*ptr == '\0') continue; dir = dir_mask(&ptr, dir); if (*ptr == '\0') continue; multi = (strchr(ptr, ',') != NULL); optr = ptr; for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { char *sptr = NULL; int ival; if (!(channels & (1 << chn))) continue; if ((dir & 1) && snd_mixer_selem_has_playback_channel(elem, chn)) { sptr = ptr; /*printf("DEBUG: ptr=%s\n",ptr);*/ if (!strncmp(ptr, "mute", 4) && snd_mixer_selem_has_playback_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &ival); if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "mute", 1, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "off", 3) && snd_mixer_selem_has_playback_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &ival); if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "off", 1, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "unmute", 6) && snd_mixer_selem_has_playback_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &ival); if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "unmute", 0, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "on", 2) && snd_mixer_selem_has_playback_switch(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &ival); if (snd_mixer_selem_set_playback_switch(elem, chn, get_bool_simple(&ptr, "on", 0, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "toggle", 6) && snd_mixer_selem_has_playback_switch(elem)) { if (firstchn || !snd_mixer_selem_has_playback_switch_joined(elem)) { snd_mixer_selem_get_playback_switch(elem, chn, &ival); if (snd_mixer_selem_set_playback_switch(elem, chn, (ival ? 1 : 0) ^ 1) >= 0) check_flag = 1; } simple_skip_word(&ptr, "toggle"); } else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') { /* tmixer-alsa FORCE to work always with % */ ptr=strcat(ptr, "%"); if (set_volume_simple(elem, chn, &ptr, 0) >= 0) check_flag = 1; } else if (simple_skip_word(&ptr, "cap") || simple_skip_word(&ptr, "rec") || simple_skip_word(&ptr, "nocap") || simple_skip_word(&ptr, "norec")) { /* nothing */ } else { okflag &= ~1; } } /* don't comment this part, need to set capture channel */ #if 1 if ((dir & 2) && snd_mixer_selem_has_capture_channel(elem, chn)) { if (sptr != NULL) ptr = sptr; sptr = ptr; if (!strncmp(ptr, "cap", 3) && snd_mixer_selem_has_capture_switch(elem)) { snd_mixer_selem_get_capture_switch(elem, chn, &ival); if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "cap", 0, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "rec", 3) && snd_mixer_selem_has_capture_switch(elem)) { snd_mixer_selem_get_capture_switch(elem, chn, &ival); if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "rec", 0, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "nocap", 5) && snd_mixer_selem_has_capture_switch(elem)) { snd_mixer_selem_get_capture_switch(elem, chn, &ival); if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "nocap", 1, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "norec", 5) && snd_mixer_selem_has_capture_switch(elem)) { snd_mixer_selem_get_capture_switch(elem, chn, &ival); if (snd_mixer_selem_set_capture_switch(elem, chn, get_bool_simple(&ptr, "norec", 1, ival)) >= 0) check_flag = 1; } else if (!strncmp(ptr, "toggle", 6) && snd_mixer_selem_has_capture_switch(elem)) { if (firstchn || !snd_mixer_selem_has_capture_switch_joined(elem)) { snd_mixer_selem_get_capture_switch(elem, chn, &ival); if (snd_mixer_selem_set_capture_switch(elem, chn, (ival ? 1 : 0) ^ 1) >= 0) check_flag = 1; } simple_skip_word(&ptr, "toggle"); } else if (isdigit(*ptr) || *ptr == '-' || *ptr == '+') { if (set_volume_simple(elem, chn, &ptr, 1) >= 0) check_flag = 1; } else if (simple_skip_word(&ptr, "mute") || simple_skip_word(&ptr, "off") || simple_skip_word(&ptr, "unmute") || simple_skip_word(&ptr, "on")) { /* nothing */ } else { okflag &= ~2; } } #endif if (okflag == 0) { if (debugflag) { if (dir & 1) error("Unknown playback setup '%s'..", ptr); if (dir & 2) error("Unknown capture setup '%s'..", ptr); } return 0; /* just skip it */ } if (!multi) ptr = optr; firstchn = 0; } } return check_flag; } static int sset(unsigned int argc, char *argv[], int roflag, int keep_handle) { int err = 0; static snd_mixer_t *handle = NULL; snd_mixer_elem_t *elem; snd_mixer_selem_id_t *sid; snd_mixer_selem_id_alloca(&sid); if (argc < 1) { fprintf(stderr, "Specify a scontrol identifier: 'name',index\n"); return 1; } if (parse_simple_id(argv[0], sid)) { fprintf(stderr, "Wrong scontrol identifier: %s\n", argv[0]); return 1; } if (!roflag && argc < 2) { fprintf(stderr, "Specify what you want to set...\n"); return 1; } if (handle == NULL) { if ((err = snd_mixer_open(&handle, 0)) < 0) { error("Mixer %s open error: %s\n", card, snd_strerror(err)); return err; } if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) { error("Mixer attach %s error: %s", card, snd_strerror(err)); snd_mixer_close(handle); handle = NULL; return err; } if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) { error("Mixer register error: %s", snd_strerror(err)); snd_mixer_close(handle); handle = NULL; return err; } err = snd_mixer_load(handle); if (err < 0) { error("Mixer %s load error: %s", card, snd_strerror(err)); snd_mixer_close(handle); handle = NULL; return err; } } elem = snd_mixer_find_selem(handle, sid); if (!elem) { if (ignore_error) return 0; error("Unable to find simple control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); snd_mixer_close(handle); handle = NULL; return -ENOENT; } /*printf("DEBUG: argc=%d argv[0]=%s argv[1]=%s\n", argc, argv[0], argv[1]);*/ if (!roflag) { /* enum control */ if (snd_mixer_selem_is_enumerated(elem)) err = sset_enum(elem, argc, argv); else err = sset_channels(elem, argc, argv); if (!err) goto done; if (err < 0) { error("Invalid command!"); goto done; } } if (!quiet) { /*printf("Simple mixer control '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid));*/ show_selem(handle, sid, " ", 1); } done: if (! keep_handle) { snd_mixer_close(handle); handle = NULL; } return err < 0 ? 1 : 0; } static void events_info(snd_hctl_elem_t *helem) { snd_ctl_elem_id_t *id; snd_ctl_elem_id_alloca(&id); snd_hctl_elem_get_id(helem, id); printf("event info: "); show_control_id(id); printf("\n"); } static void events_value(snd_hctl_elem_t *helem) { snd_ctl_elem_id_t *id; snd_ctl_elem_id_alloca(&id); snd_hctl_elem_get_id(helem, id); printf("event value: "); show_control_id(id); printf("\n"); } static void events_remove(snd_hctl_elem_t *helem) { snd_ctl_elem_id_t *id; snd_ctl_elem_id_alloca(&id); snd_hctl_elem_get_id(helem, id); printf("event remove: "); show_control_id(id); printf("\n"); } static int element_callback(snd_hctl_elem_t *elem, unsigned int mask) { if (mask == SND_CTL_EVENT_MASK_REMOVE) { events_remove(elem); return 0; } if (mask & SND_CTL_EVENT_MASK_INFO) events_info(elem); if (mask & SND_CTL_EVENT_MASK_VALUE) events_value(elem); return 0; } static void events_add(snd_hctl_elem_t *helem) { snd_ctl_elem_id_t *id; snd_ctl_elem_id_alloca(&id); snd_hctl_elem_get_id(helem, id); printf("event add: "); show_control_id(id); printf("\n"); snd_hctl_elem_set_callback(helem, element_callback); } static int ctl_callback(snd_hctl_t *ctl, unsigned int mask, snd_hctl_elem_t *elem) { if (mask & SND_CTL_EVENT_MASK_ADD) events_add(elem); return 0; } static int events(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED) { snd_hctl_t *handle; snd_hctl_elem_t *helem; int err; if ((err = snd_hctl_open(&handle, card, 0)) < 0) { error("Control %s open error: %s\n", card, snd_strerror(err)); return err; } snd_hctl_set_callback(handle, ctl_callback); if ((err = snd_hctl_load(handle)) < 0) { error("Control %s hbuild error: %s\n", card, snd_strerror(err)); return err; } for (helem = snd_hctl_first_elem(handle); helem; helem = snd_hctl_elem_next(helem)) { snd_hctl_elem_set_callback(helem, element_callback); } printf("Ready to listen...\n"); while (1) { int res = snd_hctl_wait(handle, -1); if (res >= 0) { printf("Poll ok: %i\n", res); res = snd_hctl_handle_events(handle); assert(res > 0); } } snd_hctl_close(handle); return 0; } static void sevents_value(snd_mixer_selem_id_t *sid) { printf("event value: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); } static void sevents_info(snd_mixer_selem_id_t *sid) { printf("event info: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); } static void sevents_remove(snd_mixer_selem_id_t *sid) { printf("event remove: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); } static int melem_event(snd_mixer_elem_t *elem, unsigned int mask) { snd_mixer_selem_id_t *sid; snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_get_id(elem, sid); if (mask == SND_CTL_EVENT_MASK_REMOVE) { sevents_remove(sid); return 0; } if (mask & SND_CTL_EVENT_MASK_INFO) sevents_info(sid); if (mask & SND_CTL_EVENT_MASK_VALUE) sevents_value(sid); return 0; } static void sevents_add(snd_mixer_elem_t *elem) { snd_mixer_selem_id_t *sid; snd_mixer_selem_id_alloca(&sid); snd_mixer_selem_get_id(elem, sid); printf("event add: '%s',%i\n", snd_mixer_selem_id_get_name(sid), snd_mixer_selem_id_get_index(sid)); snd_mixer_elem_set_callback(elem, melem_event); } static int mixer_event(snd_mixer_t *mixer, unsigned int mask, snd_mixer_elem_t *elem) { if (mask & SND_CTL_EVENT_MASK_ADD) sevents_add(elem); return 0; } static int sevents(int argc ATTRIBUTE_UNUSED, char *argv[] ATTRIBUTE_UNUSED) { snd_mixer_t *handle; int err; if ((err = snd_mixer_open(&handle, 0)) < 0) { error("Mixer %s open error: %s", card, snd_strerror(err)); return err; } if (smixer_level == 0 && (err = snd_mixer_attach(handle, card)) < 0) { error("Mixer attach %s error: %s", card, snd_strerror(err)); snd_mixer_close(handle); return err; } if ((err = snd_mixer_selem_register(handle, smixer_level > 0 ? &smixer_options : NULL, NULL)) < 0) { error("Mixer register error: %s", snd_strerror(err)); snd_mixer_close(handle); return err; } snd_mixer_set_callback(handle, mixer_event); err = snd_mixer_load(handle); if (err < 0) { error("Mixer %s load error: %s", card, snd_strerror(err)); snd_mixer_close(handle); return err; } printf("Ready to listen...\n"); while (1) { int res; res = snd_mixer_wait(handle, -1); if (res >= 0) { printf("Poll ok: %i\n", res); res = snd_mixer_handle_events(handle); assert(res >= 0); } } snd_mixer_close(handle); return 0; } /* * split a line into tokens * the content in the line buffer is modified */ static int split_line(char *buf, char **token, int max_token) { char *dst; int n, esc, quote; for (n = 0; n < max_token; n++) { while (isspace(*buf)) buf++; if (! *buf || *buf == '\n') return n; /* skip comments */ if (*buf == '#' || *buf == '!') return n; esc = 0; quote = 0; token[n] = buf; for (dst = buf; *buf && *buf != '\n'; buf++) { if (esc) esc = 0; else if (isspace(*buf) && !quote) { buf++; break; } else if (*buf == '\\') { esc = 1; continue; } else if (*buf == '\'' || *buf == '"') { if (! quote) { quote = *buf; continue; } else if (*buf == quote) { quote = 0; continue; } } *dst++ = *buf; } *dst = 0; } return n; } #define MAX_ARGS 32 static int exec_stdin(void) { int narg; char buf[256], *args[MAX_ARGS]; int err = 0; /* quiet = 1; */ ignore_error = 1; while (fgets(buf, sizeof(buf), stdin)) { narg = split_line(buf, args, MAX_ARGS); if (narg > 0) { if (!strcmp(args[0], "sset") || !strcmp(args[0], "set")) err = sset(narg - 1, args + 1, 0, 1); else if (!strcmp(args[0], "cset")) err = cset(narg - 1, args + 1, 0, 1); if (err < 0) return 1; } } return 0; } #ifdef HAVE_TMIXER_MAIN int tmixer_alsa(int argc, char *argv[]) { #else int main(int argc, char *argv[]) { #endif int morehelp, level = 0; int read_stdin = 0; static const struct option long_option[] = { {"help", 0, NULL, 'h'}, {"card", 1, NULL, 'c'}, {"device", 1, NULL, 'D'}, {"quiet", 0, NULL, 'q'}, {"inactive", 0, NULL, 'i'}, {"debug", 0, NULL, 'd'}, {"nocheck", 0, NULL, 'n'}, {"version", 0, NULL, 'v'}, {"abstract", 1, NULL, 'a'}, {"stdin", 0, NULL, 's'}, {NULL, 0, NULL, 0}, }; morehelp = 0; while (1) { int c; if ((c = getopt_long(argc, argv, "hc:D:qidnva:s", long_option, NULL)) < 0) break; switch (c) { case 'h': help(); return 0; case 'c': { int i; i = snd_card_get_index(optarg); if (i >= 0 && i < 32) sprintf(card, "hw:%i", i); else { fprintf(stderr, "Invalid card number.\n"); morehelp++; } } break; case 'D': strncpy(card, optarg, sizeof(card)-1); card[sizeof(card)-1] = '\0'; break; case 'q': quiet = 1; break; case 'i': level |= LEVEL_INACTIVE; break; case 'd': debugflag = 1; break; case 'n': no_check = 1; break; case 'v': printf("tmixer-alsa version (TCOS) " TCOS_VERSION "\n"); return 1; case 'a': smixer_level = 1; memset(&smixer_options, 0, sizeof(smixer_options)); smixer_options.ver = 1; if (!strcmp(optarg, "none")) smixer_options.abstract = SND_MIXER_SABSTRACT_NONE; else if (!strcmp(optarg, "basic")) smixer_options.abstract = SND_MIXER_SABSTRACT_BASIC; else { fprintf(stderr, "Select correct abstraction level (none or basic)...\n"); morehelp++; } break; case 's': read_stdin = 1; break; default: fprintf(stderr, "Invalid switch or option needs an argument.\n"); morehelp++; } } if (morehelp) { help(); return 1; } smixer_options.device = card; if (read_stdin) return exec_stdin(); if (argc - optind <= 0) { return selems(LEVEL_BASIC | level) ? 1 : 0; } if (!strcmp(argv[optind], "help")) { return help() ? 1 : 0; } else if (!strcmp(argv[optind], "info")) { return info() ? 1 : 0; } else if (!strcmp(argv[optind], "controls")) { return controls(level) ? 1 : 0; } else if (!strcmp(argv[optind], "contents")) { return controls(LEVEL_BASIC | level) ? 1 : 0; } else if (!strcmp(argv[optind], "scontrols") || !strcmp(argv[optind], "simple")) { return selems(level) ? 1 : 0; } else if (!strcmp(argv[optind], "scontents")) { return selems(LEVEL_BASIC | level) ? 1 : 0; } else if (!strcmp(argv[optind], "sset") || !strcmp(argv[optind], "set")) { return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "sget") || !strcmp(argv[optind], "get")) { return sset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "cset")) { return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 0, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "cget")) { return cset(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL, 1, 0) ? 1 : 0; } else if (!strcmp(argv[optind], "events")) { return events(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL); } else if (!strcmp(argv[optind], "sevents")) { return sevents(argc - optind - 1, argc - optind > 1 ? argv + optind + 1 : NULL); } else { fprintf(stderr, "tmixer-alsa: Unknown command '%s'...\n", argv[optind]); } return 0; }