/*----------------------------------------------------------------------------------
Name               : main.c
Author             : Marvin Raaijmakers
Description        : The main source file of keytouchd-acpid, that catches events
                     from the ACPI socket file. If such event appears in the
                     keyboard file of the current keyboard, then this program will
                     emulate a key event with the specified keycode. This is done by
                     writing events to the event device of a keyboard.
Date of last change: 02-Jul-2007
History            : 02-Jul-2007 read_event() now only cuts off a line when the line
                                 starts with "hotkey" or "button"
                     27-Dec-2006 This program is not an X client anymore. Instead it
                                 writes events to the event device of a keyboard.
                     21-Sep-2006 Added support for IBM/Lenovo notebooks (modified
                                 read_event())
                     03-Jan-2006 - Added signalhandler clean_exit()
                                 - Runs clear_acpi_key_list() when the current
                                   keyboard changes
                                 - The unused keyboard name will be freed after
                                   calling get_current_keyboard(&tmp_keyboard_name)

    Copyright (C) 2005-2007 Marvin Raaijmakers

    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 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    You can contact me at: marvinr(at)users(dot)sf(dot)net
    (replace (at) by @ and (dot) by .)
------------------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

#include <mxml.h>
#include <keytouch-acpid.h>
#include "ud_socket.h"

#define TestBit(bit, array) (array[(bit) / 8] & (1 << ((bit) % 8)))


static int find_keyboard (void);
static char *read_line (int fd);
static char *read_event (int fd);
static void clean_exit (int sig);

/* The variables below are global because
 * they are needed by clean_exit()
 */
static KTAcpiKeyList	key_list;
static KTKeyboardName	keyboard_name;
static int		keyboard_fd;


int
main (int argc, char *argv[])
{
	KTKeyboardName	tmp_keyboard_name;
	int		sock_fd;
	unsigned int	keycode,
			nerrs = 0;
	char		*event_descr;
	struct input_event event;
	
	/* Open the acpi socket */
	sock_fd = ud_connect (ACPI_SOCKETFILE);
	if (sock_fd < 0)
	{
		/*fprintf (stderr, "%s: can't open socket %s: %s\n",
		         argv[0], ACPI_SOCKETFILE, strerror(errno));*/
		return EXIT_SUCCESS;
	}
	fcntl (sock_fd, F_SETFD, FD_CLOEXEC);
	
	keyboard_fd = find_keyboard();
	if (keyboard_fd == 0) /* If no keyboard was found */
	{
		return EXIT_SUCCESS;
	}
	
	get_current_keyboard (&keyboard_name);
	read_configuration (&key_list, &keyboard_name);
	
	/* Trap key signals */
	signal (SIGINT, clean_exit);
	signal (SIGQUIT, clean_exit);
	signal (SIGTERM, clean_exit);
	
	while (1)
	{
		event_descr = read_event(sock_fd);
		if (event_descr)
		{
			get_current_keyboard (&tmp_keyboard_name);
			/* If the name of the current keyboard changed */
			if (strcmp(tmp_keyboard_name.model, keyboard_name.model) ||
			    strcmp(tmp_keyboard_name.manufacturer, keyboard_name.manufacturer))
			{
				/* We don't need the old keyboard name anymore */
				XmlFree (keyboard_name.model);
				XmlFree (keyboard_name.manufacturer);
				keyboard_name = tmp_keyboard_name;
				/* Clear the old configuration */
				clear_acpi_key_list (&key_list);
				/* Read the new configuration */
				read_configuration (&key_list, &keyboard_name);
			}
			else
			{
				XmlFree (tmp_keyboard_name.model);
				XmlFree (tmp_keyboard_name.manufacturer);
			}
			
			keycode = get_keycode (event_descr, &key_list);
			/* Is the event bound to a keycode? */
			if (keycode)
			{
				event.type = EV_KEY;
				event.code = keycode;
				event.value = 1; /* Pressed */
				write (keyboard_fd, &event, sizeof event);
				
				event.type = EV_KEY;
				event.code = keycode;
				event.value = 0; /* Released */
				write (keyboard_fd, &event, sizeof event);
			}
		}
		else if (errno == EPIPE)
		{
			KTError ("events file connection closed", "");
			return (EXIT_FAILURE);
		}
		else if (++nerrs >= ACPI_MAX_ERRS)
		{
			KTError ("too many errors reading events file - aborting", "");
			return (EXIT_FAILURE);
		}
	}
}


int
find_keyboard (void)
/*
Input:
	-
Output:
	-
Returns:
	A filedescriptor of the opened keyboard event device. If no such device was
	found then 0 will be returned.
Description:
	find_keyboard() searches an event device of a keyboard, named
	"/dev/input/eventX" (where X is an integer < 32), and returns a
	filedescriptor of the opened event device if found. If there is more than
	one keyboard event device, then the event device "/dev/input/eventX" with X
	as low as possible will be opened.
*/
{
	int evdev_num, keycode;
	int fd;
	char filename[] = "/dev/input/eventXXX";
	char key_bitmask[(KEY_MAX + 7) / 8];
	
	for (evdev_num = 0; evdev_num < 32; evdev_num++)
	{
		snprintf (filename, sizeof(filename), "/dev/input/event%d", evdev_num);
		
		fd = open (filename, O_RDWR);
		if (fd != -1)
		{
			ioctl (fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask);
			
			/* For all keycodes: */
			for (keycode = 0; keycode < BTN_MISC; keycode++)
			{
				/* Check if the event device has 'keycode' enabled */
				if (TestBit (keycode, key_bitmask))
				{
					break;
				}
			}
			
			if (keycode < BTN_MISC) /* If a key was found on this device */
			{
				return fd;
			}
			close (fd);
		}
	}
        return 0;
}




void
clean_exit (int sig)
/*
Input:
	-
Output:
	-
Globals:
	key_list
	keyboard_name
	display
Returns:
	-
Description:
	This function prepares the program to exit and then exits.
*/
{
	XmlFree (keyboard_name.model);
	XmlFree (keyboard_name.manufacturer);
	clear_acpi_key_list (&key_list);
	
	exit (EXIT_SUCCESS);
}


char
*read_event (int fd)
/*
Input:
	fd	- The socket to read from
Output:
	-
Returns:
	A pointer to the readen event description or NULL if reading the event
	failed.
Description:
	read_event() reads a line from fd and puts it in a string. The last space
	character in this string will be replaced by '\0' if the string does not
	begin with "ibm". A pointer to the resulting string will be returned. The
	string may not be modified and may not be used after calling this function
	again.
*/
{
	char *line, *last_space, *c;
	
	line = read_line(fd);
	
	/* If the line starts with "hotkey" or "button", then the last part of
	 * the line will be the value of a count. This value will be cut off.
	 * Lines that start with one of the following strings are known to not
	 * have such counter value:
	 * "Hotkey", "ibm", "video", "sony/hotkey", "hkey"
	 */
	if (line && (strncmp ("hotkey", line, 6) == 0 || strncmp("button", line, 6) == 0))
	{
		last_space = NULL;
		for (c = line; *c != '\0'; c++)
		{
			if (isspace(*c))
			{
				last_space = c;
			}
		}
		if (last_space != NULL)
		{
			*last_space = '\0';
		}
	}
	return (line);
}


/* The code below was borrowed from:
 *  acpi_listen.c - ACPI client for acpid's UNIX socket
 *
 *  Portions Copyright (C) 2003 Sun Microsystems (thockin@sun.com)
 *  Some parts (C) 2003 - Gismo / Luca Capello <luca.pca.it> http://luca.pca.it
 */
#define MAX_BUFLEN	1024
static char *
read_line(int fd)
{
	static char *buf;
	int buflen = 64;
	int i = 0;
	int r;
	int searching = 1;

	while (searching) {
		buf = realloc(buf, buflen);
		if (!buf) {
			fprintf(stderr, "ERR: malloc(%d): %s\n",
				buflen, strerror(errno));
			return NULL;
		}
		memset(buf+i, 0, buflen-i);

		while (i < buflen) {
			r = read(fd, buf+i, 1);
			if (r < 0 && errno != EINTR) {
				/* we should do something with the data */
				fprintf(stderr, "ERR: read(): %s\n",
					strerror(errno));
				return NULL;
			} else if (r == 0) {
				/* signal this in an almost standard way */
				errno = EPIPE;
				return NULL;
			} else if (r == 1) {
				/* scan for a newline */
				if (buf[i] == '\n') {
					searching = 0;
					buf[i] = '\0';
					break;
				}
				i++;
			}
		}
		if (buflen >= MAX_BUFLEN) {
			break;
		}
		buflen *= 2;
	}

	return buf;
}
