-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)
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.
--- /dev/null
+/*
+ * A Simple ALSA Volume Control via OSD
+ *
+ * (c) 2012 Martin Mares <mj@ucw.cz>
+ */
+
+#undef DEBUG
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <alsa/asoundlib.h>
+
+#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 <options>\n\
+\n\
+Options:\n\
+-a, --adjust=<percent> Adjust the control by a given amount\n\
+-D, --device=<name> ALSA device (default: `default')\n\
+-m, --mixer=<name> 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;
+}
/*
* A Simple Battery Status Display via OSD
*
- * (c) 2007--2010 Martin Mares <mj@ucw.cz>
+ * (c) 2007--2012 Martin Mares <mj@ucw.cz>
*/
+#undef DEBUG
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <getopt.h>
+#include <fcntl.h>
#include "osd.h"
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;
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; i<MAX_BATTS; i++)
{
- if (e->d_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;
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)
{
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)