]> mj.ucw.cz Git - osdd.git/commitdiff
Merge branch 'master' of ssh://git.ucw.cz/home/mj/GIT/osdd master
authorMartin Mares <mj@ucw.cz>
Tue, 8 Oct 2013 14:39:30 +0000 (16:39 +0200)
committerMartin Mares <mj@ucw.cz>
Tue, 8 Oct 2013 14:39:30 +0000 (16:39 +0200)
Makefile
README
osd-alsa.c [new file with mode: 0644]
osd-batt.c

index 40b135a094c2ba50ae6ad1f4f92a1dbae738a15f..0434fa36b2dd5631d10ff7f63d39feaf097f4c2a 100644 (file)
--- 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 c9b974da0bfe483018c2f4d074e9e4b50fa0a13a..4e1931609b76d2e6e319b411908e819523e58eb2 100644 (file)
--- 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 (file)
index 0000000..3d7bc27
--- /dev/null
@@ -0,0 +1,243 @@
+/*
+ *     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;
+}
index ace3a87f79d6e3248f88f7a3d00c809bf6d7cbc2..33ec7216fb403bb5e5b5b9a0d1c40abfe4db4352 100644 (file)
@@ -1,15 +1,18 @@
 /*
  *     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"
 
@@ -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; 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;
@@ -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)