/* ----------------------------------------------------------------------------
 * emumouse.c
 * emulates middle click and right click
 *
 * Copyright 2003, 2004 Colin Leroy (colin@colino.net).
 * scan_for_devs() Copyright 2002 Matthias Grimm (joker@cymes.de).
 * Mouse grabbing from Nicholas Hemsley <nick@blackisha.com>
 *
 * Many thanks to uinput author, <aris@cathedrallabs.org>, for his
 * really useful help.
 *
 * 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.
 * ----------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/time.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include "mouseemu.h"
#include "defkeys.h"

static int b2_mod 		= BUTTON2MOD;
static int b2_key 		= BUTTON2KEY;

static int b3_mod 		= BUTTON3MOD;
static int b3_key 		= BUTTON3KEY;
static int scroll_mod 		= SCROLLMOD;

static int b2_mod_pressed	= 0;
static int b3_mod_pressed	= 0;
static int scroll_mod_pressed	= 0;
static int typing_block_delay   = 300;

char * uinputdev		= NULL;
static int ui_fd 		= -1;

static int running 		= -1;

static kdev eventdevs[5];
static input_handler ihandler[5];

static void send_event(int type, int code, int value)
{
	struct input_event event;

	if (ui_fd < 0)
		return;

	event.type = type;
	event.code = code;
	event.value = value;
	gettimeofday(&event.time, NULL);
	if (write(ui_fd, &event, sizeof(event)) < sizeof(event))
		perror("send_event");
		
}

static void passthrough(struct input_event event)
{
	if (write(ui_fd, &event, sizeof(event)) < sizeof(event))
		perror("passthrough error");

}

static void report_click(int btn, int down)
{
	send_event(EV_KEY, btn, down);
	send_event(EV_SYN, SYN_REPORT, 0);
}

static unsigned long long last_scroll = 0;

static void report_scroll (int dy)
{
	struct timeval now;
	
	gettimeofday(&now, NULL);
	
	if ((now.tv_sec*1000000 + now.tv_usec) - last_scroll > 75000) {
		last_scroll = (now.tv_sec*1000000 + now.tv_usec);
		send_event(EV_REL, REL_WHEEL, -dy);
		send_event(EV_SYN, SYN_REPORT, 0);
	}
}

int event_parse(int code, int pressed)
{
	int retval = 0;
	
	if (!code) 
		return 1;
	
	if (code == b2_mod && b2_mod) {
		b2_mod_pressed = pressed;
		retval = 1;
	}
	if (code == b3_mod && b3_mod) {
		b3_mod_pressed = pressed;
		retval = 1;
	}
	if (code == scroll_mod && scroll_mod) {
		scroll_mod_pressed = pressed;
		retval = 1;
	}
	
	if (code == b2_key && (b2_mod_pressed || !b2_mod)) {
		report_click(BTN_MIDDLE, pressed);
		retval = 1;
	}
	if (code == b3_key && (b3_mod_pressed || !b3_mod)) {
		report_click(BTN_RIGHT, pressed);
		retval = 1;
	}

	return retval;
}

static unsigned long long last_key = 0;

static int is_modifier(struct input_event event)
{
	switch (event.code) {
		case KEY_LEFTCTRL:
		case KEY_LEFTSHIFT:
		case KEY_LEFTALT:
		case KEY_LEFTMETA:
		case KEY_RIGHTCTRL:
		case KEY_RIGHTSHIFT:
		case KEY_RIGHTALT:
		case KEY_RIGHTMETA:
		case KEY_COMPOSE:
		case KEY_CAPSLOCK:
		case KEY_NUMLOCK:
		case KEY_SCROLLLOCK:
			return 1;
		default:
			return 0;
	}
}

void keyboard_handler (int fd)
{
	struct input_event inp;
	if (read(fd, &inp, sizeof(inp)) == sizeof(inp)) {
		if (!event_parse(inp.code, inp.value) && !is_modifier(inp)) {
			last_key = (inp.time.tv_sec*1000000 + inp.time.tv_usec);
		}
	}
}

static void mouse_handler (int fd)
{
	int count;
	struct input_event inp;

	if ((count = read(fd, &inp, sizeof(inp))) == sizeof(inp)) {
		if (inp.type == EV_KEY && inp.code == BTN_LEFT) {
			if (b2_key == BTN_LEFT && b2_mod_pressed)
				report_click(BTN_MIDDLE, inp.value);
			else if (b3_key == BTN_LEFT && b3_mod_pressed)
				report_click(BTN_RIGHT, inp.value);
			else
				passthrough(inp);
		}
		else if (scroll_mod_pressed 
		      && inp.type == EV_REL 
		      && (inp.code == REL_Y || inp.code == REL_X)) {
			report_scroll (inp.value);
		} else {
			if ((inp.time.tv_sec*1000000+inp.time.tv_usec)-last_key > typing_block_delay*1000 
			|| inp.type != EV_REL)
				passthrough(inp);
		}
	}
}

void scan_for_devs()
{
	int n, m, fd;
	char filename[20];
	unsigned long bit[NBITS(EV_MAX)];
	unsigned short id[5];

	for (n = 0, m = 0; n < 5; n++) {
		sprintf(filename, "/dev/input/event%d", n);
		if ((fd = open(filename, O_RDONLY)) >= 0) {
			ioctl(fd, EVIOCGBIT(0, EV_MAX), bit);
			if (test_bit(EV_KEY, bit) && test_bit(EV_REP, bit)) {
				ioctl(fd, EVIOCGID, id);
				if (id[ID_PRODUCT] != eventdevs[m].product ||
					id[ID_VENDOR]  != eventdevs[m].vendor) {
					if (eventdevs[m].handle >= 0) {
						unregister_inputhandler(eventdevs[m].handle);
						close(eventdevs[m].handle);
					}
					eventdevs[m].handle= fd;
					eventdevs[m].product = id[ID_PRODUCT];
					eventdevs[m].vendor = id[ID_VENDOR];
					register_inputhandler(fd, keyboard_handler, 0);
				}
				m++;
			} else if (test_bit(EV_REL, bit)) {
				ioctl(fd, EVIOCGID, id);
				if (id[ID_PRODUCT] != eventdevs[m].product ||
					id[ID_VENDOR]  != eventdevs[m].vendor) {
					if (eventdevs[m].handle >= 0) {
						unregister_inputhandler(eventdevs[m].handle);
						close(eventdevs[m].handle);
					}
					eventdevs[m].handle= fd;
					eventdevs[m].product = id[ID_PRODUCT];
					eventdevs[m].vendor = id[ID_VENDOR];
					register_inputhandler(fd, mouse_handler, 1);
				}
				m++;		
			} else
				close(fd);
		}
	}
	for (; m < 5; m++) {    /* cleanup rest of list */
		eventdevs[m].product = 0;
		eventdevs[m].vendor  = 0;
		eventdevs[m].handle  = -1;
	}
}

int register_inputhandler (int fd, void (*func)(int fd), int grab)
{
	int n;

	for (n=0; n < 5; n++)
		if (ihandler[n].fd == -1) {
			ihandler[n].fd = fd;
      			ihandler[n].handler = func;
			ihandler[n].grab = grab;
			if (grab)
				ioctl(fd, EVIOCGRAB, 1);
	      		return 0;
    		}
	return -1;
}

void unregister_inputhandler (int fd)
{
	int n, found = 0;

	for (n = 0; n < 5; n++)
		if (found) {
			ihandler[n-1].fd = ihandler[n].fd;
			ihandler[n-1].handler = ihandler[n].handler;
		} else if (ihandler[n].fd == fd) {
			found = 1;
			if (ihandler[n].grab)
				ioctl(fd, EVIOCGRAB, 0);
		}
	if (found)
		ihandler[n].fd = -1;
}

int create_fdset (fd_set *watchset)
{
	int n, maxfd;

	FD_ZERO(watchset);
	for (maxfd=n=0; n < 5; n++) {
		if (ihandler[n].fd == -1) break;
		FD_SET(ihandler[n].fd, watchset);
		if (ihandler[n].fd > maxfd)
			maxfd = ihandler[n].fd;
	}
	return maxfd;
}

void call_inputhandler(fd_set *inset)
{
	int n;

	for (n=0; n < 5; n++) {
		if (ihandler[n].fd == -1) break;
		if (FD_ISSET(ihandler[n].fd, inset))
			ihandler[n].handler (ihandler[n].fd);
	}
}

void uinput_close()
{
	if (ui_fd) {
		ioctl(ui_fd, UI_DEV_DESTROY, NULL);
		close(ui_fd);
		ui_fd = -1;
	}
}

/*
 * opens uinput device
 * defaults to the specified or default path,
 * then tries some distro-specific paths
 */
int uinput_open_device(void)
{
	int fd = -1;

	printf("Trying to open %s...", uinputdev);
	fd = open (uinputdev, O_RDWR);
	printf(" %s.\n", (fd > 0)?"ok":"error");
	if (fd > 0)
		return fd;

	printf("Trying to open /dev/uinput...");
	fd = open("/dev/uinput", O_RDWR);
	printf(" %s.\n", (fd > 0)?"ok":"error");
	if (fd > 0)
		return fd;

	printf("Trying to open /dev/input/uinput...");
	fd = open("/dev/input/uinput", O_RDWR);
	printf(" %s.\n", (fd > 0)?"ok":"error");
	if (fd > 0)
		return fd;

	printf("Trying to open /dev/misc/uinput...");
	fd = open("/dev/misc/uinput", O_RDWR);
	printf(" %s.\n", (fd > 0)?"ok":"error");
	if (fd > 0)
		return fd;

	return -1;
}
int uinput_setup(void)
{
	struct uinput_user_dev device;

	if(ui_fd > 0) {
		uinput_close();
	}

	ui_fd = uinput_open_device();
	if (ui_fd < 0) {
		perror("open");
		return -1;
	}

	strcpy(device.name, "mouseemu");
	device.id.bustype = 0;
	device.id.vendor  = 0;
	device.id.product = 0;
	device.id.version = 0;

	/* mousedev only recognize our device as a mouse
	 * if it has at least two axis and one left button.
	 * Also, mouse buttons have to send sync events. 
	 */
	ioctl(ui_fd, UI_SET_EVBIT, EV_KEY);
	ioctl(ui_fd, UI_SET_EVBIT, EV_REL);
	ioctl(ui_fd, UI_SET_EVBIT, EV_SYN);

	ioctl(ui_fd, UI_SET_RELBIT, REL_X);
	ioctl(ui_fd, UI_SET_RELBIT, REL_Y);
	ioctl(ui_fd, UI_SET_RELBIT, REL_WHEEL);
	
	ioctl(ui_fd, UI_SET_KEYBIT, BTN_LEFT);
	ioctl(ui_fd, UI_SET_KEYBIT, BTN_RIGHT);
	ioctl(ui_fd, UI_SET_KEYBIT, BTN_MIDDLE);

	if (write(ui_fd, &device, sizeof(device)) < sizeof(device)) {
		perror("write");
		close (ui_fd);
		ui_fd = -1;
		return -1;
	}

	ioctl(ui_fd, UI_DEV_CREATE, NULL);

	return ui_fd;
}

void term_handler(int sig)
{
	running = -sig;
}

void install_sighandler(void)
{
	sigset_t mask;

	sigemptyset(&mask);

#ifdef SIGTERM
	signal(SIGTERM, term_handler);
	sigaddset(&mask, SIGTERM);
#endif

	sigprocmask(SIG_UNBLOCK, &mask, 0);

}

int main(int argc, char *argv[])
{
	int i=0, maxfd, val;
	fd_set inset;
	struct timeval tv;
	pid_t fpid;
	int nofork = 0;


	printf("mouseemu " VERSION " (C) Colin Leroy <colin@colino.net>\n");

	install_sighandler();

	uinputdev = DEFAULT_UINPUT;
	if (argc > 1) {
		int i = 0;
		if (!strcmp(argv[1],"-help")) {
err:
			printf("usage: %s \n"
					"\t[-middle B2_MOD B2_KEY]\n"
			       		"\t[-right  B3_MOD B3_KEY]\n"
					"\t[-scroll SCROLL_MOD]\n"
					"\t[-typing-block DELAY]\n"
					"\t[-device UINPUT_DEVICE]\n"
					"\t[-nofork]\n",
			       argv[0]);
			printf("Key codes can be found in "
			       "/usr/src/linux/include/linux/input.h,\n"
			       "or by using `showkey` in console.\n"
			       "Use decimal values. BTN_LEFT(272) is usable as "
			       "B2_KEY or B3_KEY.\n\n");
			printf("Default uinput device: " DEFAULT_UINPUT ".\n");
			printf("Default keys:\n"
					"\tMiddle click : F10 (0 68)\n"
					"\tRight click  : F11 (0 87)\n"
					"\tScroll mod.  : Alt (56)\n"
					"\tDefault blocking time while typing: 300ms\n");
			
			exit(0);
		} else {
			for (i = 1; i < argc; i++) {
				int j = i+1;
				if (!strcmp(argv[i], "-middle")) {
					if (argc > j+1) {
						b2_mod = atoi(argv[j]);
						b2_key = atoi(argv[j+1]);
					} else 
						goto err;
					continue;
				} 
				if (!strcmp(argv[i], "-right")) {
					if (argc > j+1) {
						b3_mod = atoi(argv[j]);
						b3_key = atoi(argv[j+1]);
					} else 
						goto err;
					continue;
				}
				if (!strcmp(argv[i], "-scroll")) {
					if (argc > j) {
						scroll_mod = atoi(argv[j]);
					} else 
						goto err;
					continue;					
				}
				if (!strcmp(argv[i], "-typing-block")) {
					if (argc > j) {
						typing_block_delay = atoi(argv[j]);
					} else 
						goto err;
					continue;					
				}
				if (!strcmp(argv[i], "-device")) {
					if (argc > j) {
						uinputdev = argv[j];
					} else 
						goto err;
					continue;					
				}
				if (!strcmp(argv[i], "-nofork")) {
					nofork=1;
					continue;
				}
			}
		}
	}
	printf("using (%d+%d) as middle button, (%d+%d) as right button, (%d) as scroll.\n",
		b2_mod, b2_key, b3_mod, b3_key, scroll_mod);
	printf("using %s.\n", uinputdev);
	for (i=0; i<5; i++) {
		eventdevs[i].handle = -1;
		eventdevs[i].vendor = 0;
		eventdevs[i].product= 0;
	}
	for (i=0; i<5; i++) {
		ihandler[i].handler=0;
		ihandler[i].fd=-1;
	}

	scan_for_devs();

	running = uinput_setup(); 
	if (running < 0) {
		printf("Make sure uinput module is loaded or available "
		       "in the kernel.\n");
	}

	if (nofork)
		goto startops;

	fpid = fork();
	if (fpid == -1) {
		printf("can't fork\n");
		goto startops;
	}
	if (fpid != 0) {
		exit(0);
	}

	setsid();
	fpid = fork();
	if (fpid == -1) {
		printf("can't fork\n");
		goto startops;
	}

	if (fpid != 0) {
		exit(0);
	}

	chdir("/");

startops:
	while(running > 0) {
		tv.tv_sec = 1; tv.tv_usec = 0;
		maxfd = create_fdset(&inset);
		if ((val = select (maxfd+1, &inset, NULL, NULL, &tv)) >= 0) {
			if (val == 0)
				usleep(10);
			else
				call_inputhandler(&inset);
		}
	}

	printf("cleaning...\n");

	uinput_close();

	for (i=0; i<5; i++) {
		if (ihandler[i].fd != -1) {
			unregister_inputhandler(ihandler[i].fd);
			close(ihandler[i].fd);
		}
	}
	
	exit(0);
}

