/*
 * Button daemon
 * by Pigeon (pigeon@pigeond.net)
 * 10/03/2002
 *
 * http://pigeond.net/ipaq/buttond/
 *
 * Inspiration from keyctld by Tom Vogt <tom@lemuria.org>
 *
 * TODO: Support for doing an action by holding a button down without
 *       releasing it
 *
 */

#define DEBUG 0

#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

#define VERSION					"Version 0.1"

#define TOUCHSCREEN_KEY_DEV		"/dev/touchscreen/key"
#define CONFIG_FILE				"/etc/buttond.conf"

#define BUF_SIZE				1024
#define KEY_BUFFER_SIZE			32
#define DEFAULT_KEY_TIMEOUT		300000

#define MAX(x, y)				((x > y) ? x : y)


typedef enum
{

	RECORD_DOWN		= 1,
	RECORD_UP		= 129,

	CALENDAR_DOWN	= 2,
	CALENDAR_UP		= 130,

	CONTACTS_DOWN	= 3,
	CONTACTS_UP		= 131,

	Q_DOWN			= 4,
	Q_UP			= 132,

	START_DOWN		= 5,
	START_UP		= 133,

	UP_DOWN			= 6,
	UP_UP			= 134,

	RIGHT_DOWN		= 7,
	RIGHT_UP		= 135,

	LEFT_DOWN		= 8,
	LEFT_UP			= 136,

	DOWN_DOWN		= 9,
	DOWN_UP			= 137,

	ACTION_DOWN		= 10,
	ACTION_UP		= 138,

	SUSPEND_DOWN	= 11,
	SUSPEND_UP		= 139

} button_code;

static char *button_names[] = { "Record press", "Calendar press", "Contacts press", "Q press", "Start press", "Up press", "Right press", "Left press", "Down press", "Action press", "Suspend press", "Record release", "Calendar release", "Contacts release", "Q release", "Start release", "Up release", "Right release", "Left release", "Down release", "Action release", "Suspend release" };


struct _action
{
	short key_com_size;
	char *key_com;
	char *cmd;
	struct _action *next;

};

typedef struct _action action;


/* Global action list */
static action *last_action = NULL;

/* The buffer storing current key list */
static char key_buffer[KEY_BUFFER_SIZE];
static short key_index = 0;

static struct timeval last_tv;
static long key_timeout = DEFAULT_KEY_TIMEOUT;

static short button_pressed = 0;
static short learn_mode = 0;
static short keep_going = 1;
static short raw_mode = 0;


void do_help()
{
	printf("buttond " VERSION "\n");
	printf("Written by Pigeon (pigeon@pigeond.net)\n");
	printf("http://pigeond.net/ipaq/buttond/\n\n");
	printf("Options:\n");
	printf("-h                display this help message\n");
	printf("-l                learning mode\n");
	printf("-r                raw mode, implies -l. It will output button\n");
	printf("                  combinations code only. Will not write the config\n");
	printf("                  file. Useful for gui frontend.\n");
	printf("-t <timeout>      set timeout for button combinations to <timeout>,\n");
	printf("                  in microseconds. Default is %d\n", DEFAULT_KEY_TIMEOUT);
	printf("                  (Higher value means you can press slower)\n");
	exit(1);
}


void learn_now()
{
	/* This function is lazy */

	int n;
	int len = strlen(key_buffer);

	if(raw_mode)
	{
		for(n = 0; n < len; n++)
		{
			if(n != 0)
			{
				printf(",");
			}
			printf("%d", key_buffer[n]);
		}
		printf("\n");
	}
	else
	{
		FILE *config;

		config = fopen(CONFIG_FILE, "a");

		if(config)
		{
			char buf[BUF_SIZE];


			if(!raw_mode)
			{
				fputs("\n#\n# Added by buttond learning mode\n# ", config);

				for(n = 0; n < len; n++)
				{
					int index = key_buffer[n];

					if(n != 0)
					{
						fputs(",", config);
					}

					if(index < 12)
					{
						index -= 1;
					}
					else
					{
						index -= 118;
					}

#if 0
					switch(key_buffer[n])
					{
						case RECORD_DOWN:
						case RECORD_UP:
						case CALENDAR_DOWN:
						case CALENDAR_UP:
						case CONTACTS_DOWN:
						case CONTACTS_UP:
						case Q_DOWN:
						case Q_UP:
						case START_DOWN:
						case START_UP:
						case UP_DOWN:
						case UP_UP:
						case RIGHT_DOWN:
						case RIGHT_UP:
						case LEFT_DOWN:
						case LEFT_UP:
						case DOWN_DOWN:
						case DOWN_UP:
						case ACTION_DOWN:
						case ACTION_UP:
						case SUSPEND_DOWN:
						case SUSPEND_UP:
						default:
							break;

					}
#endif

					fputs(button_names[index], config);

				}

				fputs("\n", config);

			}

			for(n = 0; n < len; n++)
			{
				if(n != 0)
				{
					fputs(",", config);
				}
				sprintf(buf, "%d", key_buffer[n]);
				fputs(buf, config);
			}

			fputs(":<Add your own command here>\n", config);

			fclose(config);

			printf("Button combinations appended to " CONFIG_FILE "\n");

		}

	}

}


void free_key_buffer()
{
	memset(key_buffer, 0, KEY_BUFFER_SIZE);
	key_index = 0;
}


void check_key_buffer()
{
	action *a;

	for(a = last_action; a; a = a->next)
	{
		/* Compare with the larger one */
		int max_size = MAX((unsigned int) a->key_com_size, strlen(key_buffer));

		if(strncmp(a->key_com, key_buffer, max_size) == 0)
		{
			pid_t pid;

			free_key_buffer();

			pid = fork();

			if(pid == 0)
			{
#if DEBUG
				printf("Running %s\n", a->cmd);
#endif
				execlp("/bin/sh", "sh", "-c", a->cmd, NULL);
				printf("exec() failed.\n");
				exit(-1);
			}
			else if(pid == -1)
			{
				printf("Cannot fork!\n");
				exit(-1);
			}

			break;
		}
	}
}


void free_config()
{
	action *a = last_action;

	while(a)
	{
		action *next = a->next;
		free(a->key_com);
		free(a->cmd);
		free(a);
		a = next;
	}

	last_action = NULL;

}


int setup_config()
{
	int ret = -1;
	FILE *config;
	char buf[BUF_SIZE];
	
	config = fopen(CONFIG_FILE, "r");

	if(config)
	{
		while(fgets(buf, sizeof(buf) - 1, config))
		{
			char button_str[BUF_SIZE], cmd[BUF_SIZE];

#if DEBUG
			printf("Got line: %s", buf);
#endif
			if(buf[0] == '#')
			{
				continue;
			}

			if(sscanf(buf, "%[^:]:%[^\n]", button_str, cmd) == 2)
			{
				int index = 0;
				char *b = button_str;
				char bbuf[BUF_SIZE];
				action *new_action;

				memset(bbuf, 0, BUF_SIZE);

				bbuf[index++] = atoi(b);
#if DEBUG
				printf("Got key %d\n", atoi(b));
#endif

				for(; (b = strchr(b, ',')); b++)
				{
#if DEBUG
					printf("Got key %d\n", atoi(b + 1));
#endif
					bbuf[index++] = atoi(b + 1);
				}

				/* Set it up! */
				new_action = malloc(sizeof(action));

				new_action->key_com= malloc(index * sizeof(char));
				strcpy(new_action->key_com, bbuf);

				new_action->key_com_size = index;
				new_action->cmd = strdup(cmd);
				new_action->next = last_action;
				last_action = new_action;

			}
#if DEBUG
			else
			{
				printf("Ignoring line %s\n", buf);
			}
#endif
		}

		fclose(config);

		ret = 0;

	}
	else
	{
		printf(CONFIG_FILE " does not exist...\n");
	}

#if DEBUG
	{
		action *a;
		
		for(a = last_action; a; a = a->next)
		{
			printf("Loaded command:\n");
			printf("[%s][%s]\n", a->key_com, a->cmd);
		}
	}
#endif

	return ret;

}


void signal_handler(int signum)
{
	switch(signum)
	{
		case SIGHUP:
#if DEBUG
			printf("Reloading config...\n");
#endif
			free_config();
			setup_config();
			break;

		case SIGCHLD:
			{
				pid_t pid;

				do
				{
					pid = waitpid(-1, NULL, WNOHANG);
				}
				while(pid > 0);
			}

			break;

		default:

			break;

	}
}


void setup_signal_handler()
{
	struct sigaction action;

	action.sa_handler = signal_handler;
	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;

	if((sigaction(SIGHUP, &action, NULL)) < 0)
	{
		perror("sigaction SIGHUP");
		exit(1);
	}

	action.sa_handler = signal_handler;
	sigemptyset(&action.sa_mask);
	action.sa_flags = 0;

	if((sigaction(SIGCHLD, &action, NULL)) < 0)
	{
		perror("sigaction SIGCHLD");
		exit(1);
	}
}


void main_loop(int fd)
{
	fd_set read_set;
	int max_fd;

	int n;
	int ret;
	int readcount;

	struct timeval select_tv;
	struct timeval this_tv;
	struct timeval timeout_tv;
	struct timeval result_tv;

	char buffer[BUF_SIZE];


	max_fd = fd + 1;

	timeout_tv.tv_sec = 0;
	timeout_tv.tv_usec = key_timeout;

	while(keep_going)
	{

		FD_ZERO(&read_set);
		FD_SET(fd, &read_set);

		select_tv.tv_sec = 0;
		select_tv.tv_usec = 100000;

		if((ret = select(max_fd, &read_set, NULL, NULL, &select_tv)) > 0)
		{
			if(FD_ISSET(fd, &read_set))
			{
				readcount = read(fd, buffer, sizeof(buffer));

				/* Usually readcount is always one, but anyway */
				for(n = 0; n < readcount; n++)
				{
					/* Fill the key buffer */

					key_buffer[key_index++] = (int) buffer[n];

					button_pressed = 1;

					if(key_index == KEY_BUFFER_SIZE)
					{
						key_index = 0;
					}

				}

				last_tv = this_tv;

			}

		}

		/* Check if the key buffer is old enough */
		timeradd(&last_tv, &timeout_tv, &result_tv);
		gettimeofday(&this_tv, NULL);

		if(timercmp(&this_tv, &result_tv, >))
		{
			if(learn_mode && button_pressed)
			{
				break;
			}
			else
			{
				check_key_buffer();
				free_key_buffer();
			}
		}

#if DEBUG
		for(n = 0; n < KEY_BUFFER_SIZE; n++)
		{
			printf("%03d ", key_buffer[n]);
		}
		printf("\n");
#endif

	}


}


int main(int argc, char * argv[])
{
	int ret = 0;
	int fd;

	if(argc > 1)
	{
		int optchar;
		static char optstring[] = "rnhlt:";

		while((optchar = getopt(argc, argv, optstring)) != -1)
		{
			switch(optchar)
			{
				case 'h':
					do_help();
					break;
				
				case 'r':
					raw_mode = 1;
					learn_mode = 1;
				
				case 'l':
					learn_mode = 1;
					break;

				case 't':
					key_timeout = atol(optarg);
					break;

				default:
					break;
			}
		}
	}

	fd = open(TOUCHSCREEN_KEY_DEV, O_RDONLY | O_NONBLOCK);

	if(fd == -1)
	{
		printf("Cannot open " TOUCHSCREEN_KEY_DEV "\n");
		return 1;
	}

	memset(key_buffer, 0, KEY_BUFFER_SIZE);

	if(learn_mode)
	{
		/* Learning mode, main_loop will only run once */
		if(!raw_mode)
		{
			printf("Learning mode...\n");
			printf("Press your button combinations...\n");
		}
		main_loop(fd);
	}
	else
	{
		if((ret = setup_config()) == 0)
		{
			setup_signal_handler();
			main_loop(fd);
		}
	}

	close(fd);

	if(learn_mode)
	{
		learn_now();
	}
	else
	{
		free_config();
	}

	return ret;

}


/*
 * $Log$
 */

