From: Martin Mares Date: Tue, 8 Oct 2013 14:39:30 +0000 (+0200) Subject: Merge branch 'master' of ssh://git.ucw.cz/home/mj/GIT/osdd X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=390865c81e3eadcfc8ccac3b9cd0b4551f908b67;hp=4e31475880ddb439d98bfdc53b9dfe8c342ba95f;p=osdd.git Merge branch 'master' of ssh://git.ucw.cz/home/mj/GIT/osdd --- diff --git a/Makefile b/Makefile index 40b135a..0434fa3 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,26 @@ -VERSION=1.0 +VERSION=1.1 ARCHIVE=osdd-$(VERSION).tar.gz CFLAGS=-O2 -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -std=gnu99 -all: osdd osdc osd-batt +all: osdd osdc osd-batt osd-alsa osdd: osdd.o util.o osdc: osdc.o util.o client.o osd-batt: osd-batt.o util.o client.o +osd-alsa: osd-alsa.o util.o client.o osdd.o: CFLAGS+=$(shell xosd-config --cflags) -osdd: LDFLAGS+=$(shell xosd-config --libs) +osdd: LDLIBS+=$(shell xosd-config --libs) -osdc: LDFLAGS+=-lX11 -osd-batt: LDFLAGS+=-lX11 +osdc: LDLIBS+=-lX11 +osd-batt: LDLIBS+=-lX11 + +osd-alsa.o: CFLAGS+=$(shell pkg-config --cflags alsa) +osd-alsa: LDLIBS+=$(shell pkg-config --libs alsa) -lX11 clean: - rm -f *~ *.o TAGS core osdd osdc osd-batt + rm -f *~ *.o TAGS core osdd osdc osd-batt osd-alsa release: git tag v$(VERSION) diff --git a/README b/README index c9b974d..4e19316 100644 --- a/README +++ b/README @@ -69,3 +69,5 @@ osd-batt A slightly more complex client written in C, showing the current status of laptop batteries. It can be asked to show status immediately or to run in background and croak whenever the battery is low. + +osd-alsa Adjust volume of an ALSA mixer channel. Also written in C. diff --git a/osd-alsa.c b/osd-alsa.c new file mode 100644 index 0000000..3d7bc27 --- /dev/null +++ b/osd-alsa.c @@ -0,0 +1,243 @@ +/* + * A Simple ALSA Volume Control via OSD + * + * (c) 2012 Martin Mares + */ + +#undef DEBUG + +#include +#include +#include +#include +#include + +#include "osd.h" + +static char *alsa_device = "default"; +static char *mixer_control = "Master"; +static int adjust_by; +static int want_mute = -1; + +static snd_mixer_t *mixer; +static snd_mixer_elem_t *elem; + +static void init_mixer(void) +{ + int err; + + if (err = snd_mixer_open(&mixer, 0)) + die("snd_mixer_open failed: error %d", err); + + if (err = snd_mixer_attach(mixer, alsa_device)) + die("snd_mixer_attach failed: error %d", err); + + if (err = snd_mixer_selem_register(mixer, NULL, NULL)) + die("snd_mixer_selem_register failed: error %d", err); + + if (err = snd_mixer_load(mixer)) + die("snd_mixer_load: error %d", err); + + for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) + { + const char *name = snd_mixer_selem_get_name(elem); + int index = snd_mixer_selem_get_index(elem); + if (!strcmp(name, mixer_control) || index) + { + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE) + die("Unable to handle non-simple mixer controls"); + if (!snd_mixer_selem_is_active(elem)) + die("Selected mixer control is not active"); + DBG("Found mixer control %s[%d]\n", name, index); + return; + } + } + + die("Unable to find mixer control %s", mixer_control); +} + +static int get_mute(void) +{ + int mute_on = 0, mute_off = 0; + + if (snd_mixer_selem_has_playback_switch(elem)) + { + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + { + int val; + if (snd_mixer_selem_get_playback_switch(elem, ch, &val)) + { + if (val) + mute_off++; + else + mute_on++; + } + } + } + DBG("Mute: on=%d off=%d\n", mute_on, mute_off); + return !!mute_on; +} + +static long get_volume(long *pmin, long *pmax) +{ + long min, max, curr=0; + if (!snd_mixer_selem_has_playback_volume(elem) || + snd_mixer_selem_get_playback_volume_range(elem, &min, &max)) + { + *pmin = *pmax = 0; + return 0; + } + + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + { + long val; + if (snd_mixer_selem_get_playback_volume(elem, ch, &val)) + { + if (val > curr) + curr = val; + } + } + + DBG("Volume: min=%ld max=%ld curr=%ld\n", min, max, curr); + *pmin = min; + *pmax = max; + return curr; +} + +static int vol_to_perc(long curr, long min, long max) +{ + return (100LL * (curr-min) + (max-min)/2) / (max-min); +} + +static long perc_to_vol(int perc, long min, long max) +{ + return ((long long) perc * (max-min) + 50) / 100; +} + +static void show_mixer(void) +{ + long min, max; + long curr = get_volume(&min, &max); + int muted = get_mute(); + + struct osd_msg *msg = osd_new_msg(); + osd_add_line(msg, "min-duration", "0"); + char buf[256]; + snprintf(buf, sizeof(buf), "%s volume", mixer_control); + osd_add_line(msg, NULL, buf); + osd_add_line(msg, NULL, ""); + if (muted) + osd_add_line(msg, NULL, "[mute]"); + else if (min < max) + { + snprintf(buf, sizeof(buf), "%d", vol_to_perc(curr, min, max)); + osd_add_line(msg, "slider", buf); + } + osd_send(msg); +} + + +static void set_mute(void) +{ + if (want_mute < 0) + return; + + if (want_mute == 2) + want_mute = !get_mute(); + + if (snd_mixer_selem_has_playback_switch(elem)) + { + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + snd_mixer_selem_set_playback_switch(elem, ch, !want_mute); + } +} + +static void set_volume(void) +{ + if (!adjust_by) + return; + + long min, max; + long curr = get_volume(&min, &max); + int perc = vol_to_perc(curr, min, max); + + DBG("Volume: have %d %ld\n", perc, curr); + perc += adjust_by; + if (perc < 0) + perc = 0; + if (perc > 100) + perc = 100; + curr = perc_to_vol(perc, min, max); + curr = min + (((long long) perc * (max-min) + 50) / 100); + DBG("Volume: want %d %ld\n", perc, curr); + + for (snd_mixer_selem_channel_id_t ch=0; ch < SND_MIXER_SCHN_LAST; ch++) + snd_mixer_selem_set_playback_volume(elem, ch, curr); +} + +static void NONRET +usage(void) +{ + fprintf(stderr, "\ +Usage: osd-alsa \n\ +\n\ +Options:\n\ +-a, --adjust= Adjust the control by a given amount\n\ +-D, --device= ALSA device (default: `default')\n\ +-m, --mixer= Name of mixer control (default: `Master')\n\ +-0, --mute Mute the control\n\ +-t, --toggle Mute/unmute the control\n\ +-1, --unmute Unmute the control\n\ +"); + exit(1); +} + +static const char short_opts[] = "01a:c:D:t"; + +static const struct option long_opts[] = { + { "adjust", required_argument, NULL, 'a' }, + { "device", required_argument, NULL, 'D' }, + { "mixer", required_argument, NULL, 'm' }, + { "mute", no_argument, NULL, '0' }, + { "toggle", no_argument, NULL, 't' }, + { "unmute", no_argument, NULL, '1' }, + { NULL, 0, NULL, 0 }, +}; + +int main(int argc, char **argv) +{ + int opt; + while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0) + switch (opt) + { + case '0': + want_mute = 1; + break; + case '1': + want_mute = 0; + break; + case 'a': + adjust_by = atoi(optarg); + break; + case 'D': + alsa_device = optarg; + break; + case 'm': + mixer_control = optarg; + break; + case 't': + want_mute = 2; + break; + default: + usage(); + } + if (optind < argc) + usage(); + + init_mixer(); + osd_init(); + set_mute(); + set_volume(); + show_mixer(); + return 0; +} diff --git a/osd-batt.c b/osd-batt.c index ace3a87..33ec721 100644 --- a/osd-batt.c +++ b/osd-batt.c @@ -1,15 +1,18 @@ /* * A Simple Battery Status Display via OSD * - * (c) 2007--2010 Martin Mares + * (c) 2007--2012 Martin Mares */ +#undef DEBUG + #include #include #include #include #include #include +#include #include "osd.h" @@ -17,7 +20,7 @@ static int check_mode; static int check_every; static int warn_threshold = 600; -static int total_full, total_capa, discharge_rate; +static int total_now, total_full, discharge_rate; static int charge_time, discharge_time; static int ac_online; static unsigned int present_mask, charge_mask, discharge_mask; @@ -25,61 +28,104 @@ static unsigned int present_mask, charge_mask, discharge_mask; static unsigned int last_charge_mask, last_discharge_mask; static int last_ac_online = -1; -static char *parse_line(char *line) +#define MAX_BATTS 4 +#define BATT_NAME_LEN 32 +static char batt_names[MAX_BATTS][BATT_NAME_LEN]; + +static char sys_dir[256]; +#define BUFSIZE 256 + +static int sys_read(char *buf, char *attribute) { - char *t = strchr(line, '\n'); - if (t) - *t = 0; - char *val = strchr(line, ':'); - if (!val) - return NULL; - *val++ = 0; - while (*val == ' ' || *val == '\t') - *val++ = 0; - return val; + char name[256]; + snprintf(name, sizeof(name), "%s/%s", sys_dir, attribute); + + int fd = open(name, O_RDONLY); + if (fd < 0) + return 0; + + int n = read(fd, buf, BUFSIZE); + close(fd); + if (n < 0) + return 0; + + buf[BUFSIZE-1] = 0; + char *nl = strchr(buf, '\n'); + if (nl) + *nl = 0; + DBG("\t%s=%s\n", attribute, buf); + return 1; } -static void scan_ac(void) +static int sys_read_int(char *attribute, int default_value) { - ac_online = 0; + char buf[BUFSIZE]; + if (!sys_read(buf, attribute) || !buf[0]) + return default_value; + else + return atoi(buf); +} - const char dir[] = "/proc/acpi/ac_adapter"; - DIR *d = opendir(dir); - if (!d) - return; +static void parse_ac(void) +{ + ac_online = sys_read_int("online", 0); +} - struct dirent *e; - while (e = readdir(d)) +static int get_batt_id(char *batt_name) +{ + for (int i=0; id_name[0] == '.') - continue; - char n[sizeof(dir) + 1 + strlen(e->d_name) + 6]; - sprintf(n, "%s/%s/state", dir, e->d_name); - FILE *f = fopen(n, "r"); - if (!f) - continue; - char line[1024]; - while (fgets(line, sizeof(line)-1, f)) + if (!strcmp(batt_names[i], batt_name)) + return i; + if (!batt_names[i][0]) { - char *val = parse_line(line); - if (!val) - continue; - if (!strcmp(line, "state") && !strcmp(val, "on-line")) - ac_online = 1; + snprintf(batt_names[i], BATT_NAME_LEN, "%s", batt_name); + return i; } - fclose(f); } - closedir(d); + return MAX_BATTS; } -static void scan_batt(void) +static void parse_batt(char *batt_name) { + int batt_id = get_batt_id(batt_name); + DBG("\t-> id %d\n", batt_id); + + if (!sys_read_int("present", 1)) + return; + + char status[BUFSIZE]; + int charging = sys_read(status, "status") && !strcmp(status, "Charging"); + int charge_full = sys_read_int("charge_full", 0); + int charge_now = sys_read_int("charge_now", 0); + int current_now = sys_read_int("current_now", 0); + + present_mask |= 1 << batt_id; + total_now += charge_now; + total_full += charge_full; + if (charging && current_now > 0) + { + charge_mask |= 1 << batt_id; + int ch = (long long)(charge_full - charge_now)*3600 / current_now; + if (ch > charge_time) + charge_time = ch; + } + else if (current_now > 0) + { + discharge_mask |= 1 << batt_id; + discharge_rate += current_now; + } +} + +static void scan(void) +{ + ac_online = 0; charge_time = discharge_time = 0; - total_full = total_capa = 0; + total_now = total_full = 0; discharge_rate = 0; present_mask = charge_mask = discharge_mask = 0; - const char dir[] = "/proc/acpi/battery"; + const char dir[] = "/sys/class/power_supply"; DIR *d = opendir(dir); if (!d) return; @@ -88,76 +134,28 @@ static void scan_batt(void) while (e = readdir(d)) { if (e->d_name[0] == '.') - continue; - char n[sizeof(dir) + 1 + strlen(e->d_name) + 6]; - const char * const names[] = { "state", "info" }; - int present = 0; - int rate = 0; - int charging = 0; - int remains = 0; - int last_full = 0; - int batt_id = -1; - for (int i=0; i<2; i++) - { - sprintf(n, "%s/%s/%s", dir, e->d_name, names[i]); - FILE *f = fopen(n, "r"); - if (!f) - continue; - char line[1024]; - while (fgets(line, sizeof(line)-1, f)) - { - char *val = parse_line(line); - if (!val) - continue; - // printf("<%s> <%s>\n", line, val); - if (!strcmp(line, "present")) - present = !strcmp(val, "yes"); - else if (!strcmp(line, "charging state")) - charging = !strcmp(val, "charging"); - else if (!strcmp(line, "present rate")) - rate = atol(val); - else if (!strcmp(line, "remaining capacity")) - remains = atol(val); - else if (!strcmp(line, "last full capacity")) - last_full = atol(val); - else if (!strcmp(line, "serial number")) - batt_id = atol(val); - } - fclose(f); - } - if (present) - { - if (batt_id < 0 || batt_id > 31) - batt_id = 0; - present_mask |= 1 << batt_id; - total_full += remains; - total_capa += last_full; - if (charging) - { - charge_mask |= 1 << batt_id; - int ch = (last_full - remains)*3600 / rate; - if (ch > charge_time) - charge_time = ch; - } - else if (rate > 0) - { - discharge_mask |= 1 << batt_id; - discharge_rate += rate; - } - } + continue; + snprintf(sys_dir, sizeof(sys_dir), "%s/%s", dir, e->d_name); + DBG("%s\n", sys_dir); + + char type[BUFSIZE]; + if (!sys_read(type, "type")) + continue; + + if (!strcmp(type, "Mains")) + parse_ac(); + else if (!strcmp(type, "Battery")) + parse_batt(e->d_name); } if (discharge_rate) - discharge_time = total_full*3600 / discharge_rate; + discharge_time = (long long) total_now*3600 / discharge_rate; else discharge_time = 1000000; closedir(d); -} - -static void scan(void) -{ - scan_ac(); - scan_batt(); + DBG("=> Capacity: now=%d full=%d\n", total_now, total_full); + DBG("=> Charge: mask=%d time=%d\n", charge_mask, charge_time); + DBG("=> Discharge: mask=%d rate=%d time=%d\n", discharge_mask, discharge_rate, discharge_time); } static char *batt_mask(char *p, unsigned int mask) @@ -181,8 +179,8 @@ static void show(void) { char status[256]; char *p = status; - if (total_capa) - p += sprintf(p, "%d%%", 100*total_full/total_capa); + if (total_full) + p += sprintf(p, "%d%%", (int)((long long)100*total_now/total_full)); else p += sprintf(p, "??%%"); if (discharge_mask && discharge_time < 1000000)