/*
 * volumed - volume daemon for SK-8135 keyboards
 *
 * compile with -lasound and proper include paths for the evdev
 *
 * Copyright (C) 2005 - Jochen Eisinger <eisinger@informatik.uni-freiburg.de>
 *
 * based on evtest.c, Copyright (c) 1999-2000 Vojtech Pavlik
 *          amixer.c, Copyright (c) 1999-2000 by Jaroslav Kysela
 * 
 * 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 <stdint.h>

#include <linux/input.h>

#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <syslog.h>
#include <alsa/asoundlib.h>

#ifndef REL_VOLUME
#define REL_VOLUME 0x0a
#endif

#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define OFF(x)  ((x)%BITS_PER_LONG)
#define BIT(x)  (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array)	((array[LONG(bit)] >> OFF(bit)) & 1)

int main (int argc, char **argv)
{
	int fd, rd, i;
	struct input_event ev[64];
	unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
	int abs[5];
	snd_mixer_t *handle;
	snd_mixer_elem_t *elem;
	snd_mixer_selem_id_t *sid;
	snd_mixer_selem_channel_id_t chn;
	long pmin, pmax, lval, amin, amax;

	if (argc < 2) {
		printf("Usage: volumed /dev/input/eventX\n");
		printf("Where X = input device number\n");
		return 1;
	}

	if ((fd = open(argv[argc - 1], O_RDONLY)) < 0) {
		perror("volumed");
		return 1;
	}

	memset(bit, 0, sizeof(bit));
	ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);

	if (test_bit(EV_ABS, bit[0])) {
		ioctl(fd, EVIOCGBIT(EV_ABS, KEY_MAX), bit[EV_ABS]);
		if (test_bit(ABS_VOLUME, bit[EV_ABS])) {
			ioctl(fd, EVIOCGABS(EV_ABS), abs);
			amin = abs[1];
			amax = abs[2];
		}
	}
		

	snd_mixer_selem_id_alloca(&sid);
	snd_mixer_selem_id_set_index(sid, 0);
	snd_mixer_selem_id_set_name(sid, "PCM");

	if (snd_mixer_open(&handle, 0) < 0) {
		fputs("volumed: could not open mixer\n", stderr);
		return 1;
	}

	if (snd_mixer_attach(handle, "default") < 0) {
		fputs("volumed: could not attach to soundcard\n", stderr);
		return 1;
	}

	if (snd_mixer_selem_register(handle, NULL, NULL) < 0) {
		fputs("volumed: could not register mixer\n", stderr);
		snd_mixer_close(handle);
		return 1;
	}

	if (snd_mixer_load(handle) < 0) {
		fputs("volumed: could not load mixer\n", stderr);
		snd_mixer_close(handle);
		return 1;
	}

	if ((elem = snd_mixer_find_selem(handle, sid)) == NULL) {
		fputs("volumed: could not find PCM mixer\n", stderr);
		snd_mixer_close(handle);
		return 1;
	}

	snd_mixer_selem_get_playback_volume_range(elem, &pmin, &pmax);
	
	if (fork())
		return 0;

	close(0);
	close(1);
	close(2);

	openlog("volumed", 0, LOG_DAEMON);

	while (1) {
		rd = read(fd, ev, sizeof(struct input_event) * 64);

		if (rd < (int) sizeof(struct input_event)) {
			syslog(LOG_ERR, "error reading from event device... exiting\n");
			return 1;
		}

		for (i = 0; i < rd / sizeof(struct input_event); i++)
			if (((ev[i].type == EV_REL) && ((ev[i].code == REL_VOLUME) || (ev[i].code == REL_DIAL))) || ((ev[i].type == EV_ABS) && (ev[i].code == ABS_VOLUME))) {
				int v = ev[i].value;
				for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) {
					if (snd_mixer_selem_has_playback_channel(elem, chn)) {
						if (snd_mixer_selem_has_playback_volume(elem)) {
							if (ev[i].type == EV_REL) {
								snd_mixer_selem_get_playback_volume(elem, chn, &lval);
								lval += v;
								if (lval < pmin)
									lval = pmin;
								if (lval > pmax)
									lval = pmax;
							} else {
								float percent = (float)(v - amin)/(amax - amin);
								lval = pmin + percent * (pmax - pmin);
							}
								
							snd_mixer_selem_set_playback_volume(elem, chn, lval);
						}
					}
				}
			}	

	}
}
