OpenBSD-4.6/usr.sbin/wsmoused/wsmoused.c

Compare this file to the similar file:
Show the results in this format:

/* $OpenBSD: wsmoused.c,v 1.25 2009/06/21 16:13:18 jacekm Exp $ */

/*
 * Copyright (c) 2001 Jean-Baptiste Marchand, Julien Montagne and Jerome Verdon
 *
 * Copyright (c) 1998 by Kazutaka Yokota
 *
 * Copyright (c) 1995 Michael Smith
 *
 * Copyright (c) 1993 by David Dawes <dawes@xfree86.org>
 *
 * Copyright (c) 1990,91 by Thomas Roell, Dinkelscherben, Germany.
 *
 * All rights reserved.
 *
 * Most of this code was taken from the FreeBSD moused daemon, written by
 * Michael Smith. The FreeBSD moused daemon already contained code from the
 * Xfree Project, written by David Dawes and Thomas Roell and Kazutaka Yokota.
 *
 * Adaptation to OpenBSD was done by Jean-Baptiste Marchand, Julien Montagne
 * and Jerome Verdon.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by
 *      David Dawes, Jean-Baptiste Marchand, Julien Montagne, Thomas Roell,
 *      Michael Smith, Jerome Verdon and Kazutaka Yokota.
 * 4. The name authors may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 */

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/tty.h>
#include <dev/wscons/wsconsio.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <syslog.h>

#include "mouse_protocols.h"
#include "wsmoused.h"

#define	DEFAULT_TTY	"/dev/ttyCcfg"
#define	DEFAULT_PIDFILE	"/var/run/wsmoused.pid"

extern char *__progname;
extern char *mouse_names[];

int debug = 0;
int background = FALSE;
int nodaemon = FALSE;
int identify = FALSE;
char *pidfile = NULL;

mouse_t mouse = {
	.flags = 0,
	.portname = NULL,
	.ttyname = NULL,
	.proto = P_UNKNOWN,
	.baudrate = 1200,
	.old_baudrate = 1200,
	.rate = MOUSE_RATE_UNKNOWN,
	.resolution = MOUSE_RES_UNKNOWN,
	.zmap = 0,
	.wmode = 0,
	.mfd = -1,
	.clickthreshold = 500,	/* 0.5 sec */
};

/* identify the type of a wsmouse supported mouse */
void
wsmouse_identify(void)
{
	unsigned int type;

	if (mouse.mfd != -1) {
		if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) == -1)
			err(1, "can't detect mouse type");

		printf("wsmouse supported mouse: ");
		switch (type) {
		case WSMOUSE_TYPE_VSXXX:
			printf("DEC serial\n");
			break;
		case WSMOUSE_TYPE_PS2:
			printf("PS/2 compatible\n");
			break;
		case WSMOUSE_TYPE_USB:
			printf("USB\n");
			break;
		case WSMOUSE_TYPE_LMS:
			printf("Logitech busmouse\n");
			break;
		case WSMOUSE_TYPE_MMS:
			printf("Microsoft InPort mouse\n");
			break;
		case WSMOUSE_TYPE_TPANEL:
			printf("Generic Touch Panel\n");
			break;
		case WSMOUSE_TYPE_NEXT:
			printf("NeXT\n");
			break;
		case WSMOUSE_TYPE_ARCHIMEDES:
			printf("Archimedes\n");
			break;
		case WSMOUSE_TYPE_ADB:
			printf("ADB\n");
			break;
		case WSMOUSE_TYPE_HIL:
			printf("HP-HIL\n");
			break;
		case WSMOUSE_TYPE_LUNA:
			printf("Omron Luna\n");
			break;
		case WSMOUSE_TYPE_DOMAIN:
			printf("Apollo Domain\n");
			break;
		case WSMOUSE_TYPE_SUN:
			printf("Sun\n");
			break;
		default:
			printf("Unknown\n");
			break;
		}
	} else
		warnx("unable to open %s", mouse.portname);
}

/* wsmouse_init : init a wsmouse compatible mouse */
void
wsmouse_init(void)
{
	unsigned int res = WSMOUSE_RES_MIN;
	unsigned int rate = WSMOUSE_RATE_DEFAULT;

	ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
	ioctl(mouse.mfd, WSMOUSEIO_SRATE, &rate);
}

/*
 * Buttons remapping
 */

/* physical to logical button mapping */
static int p2l[MOUSE_MAXBUTTON] = {
	MOUSE_BUTTON1,	MOUSE_BUTTON2,	MOUSE_BUTTON3,	MOUSE_BUTTON4,
	MOUSE_BUTTON5,	MOUSE_BUTTON6,	MOUSE_BUTTON7,	MOUSE_BUTTON8,
};

static char *
skipspace(char *s)
{
	while (isspace(*s))
		++s;
	return s;
}

/* mouse_installmap : install a map between physical and logical buttons */
static int
mouse_installmap(char *arg)
{
	int pbutton;
	int lbutton;
	char *s;

	while (*arg) {
		arg = skipspace(arg);
		s = arg;
		while (isdigit(*arg))
			++arg;
		arg = skipspace(arg);
		if ((arg <= s) || (*arg != '='))
			return FALSE;
		lbutton = atoi(s);

		arg = skipspace(++arg);
		s = arg;
		while (isdigit(*arg))
			++arg;
		if (arg <= s || (!isspace(*arg) && *arg != '\0'))
			return FALSE;
		pbutton = atoi(s);

		if (lbutton <= 0 || lbutton > MOUSE_MAXBUTTON)
			return FALSE;
		if (pbutton <= 0 || pbutton > MOUSE_MAXBUTTON)
			return FALSE;
		p2l[pbutton - 1] = lbutton - 1;
	}
	return TRUE;
}

/* mouse_map : converts physical buttons to logical buttons */
static void
mouse_map(struct wscons_event *orig, struct wscons_event *mapped)
{
	mapped->type = orig->type;
	mapped->value = p2l[orig->value];
}

/* terminate signals handler */
static void
terminate(int sig)
{
	struct wscons_event event;
	unsigned int res;

	if (mouse.mfd != -1) {
		event.type = WSCONS_EVENT_WSMOUSED_OFF;
		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event);
		res = WSMOUSE_RES_DEFAULT;
		ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
		close(mouse.mfd);
		mouse.mfd = -1;
	}
	if (pidfile != NULL)
		unlink(pidfile);
	_exit(0);
}

/* buttons status (for multiple click detection) */
static struct {
	int count;		/* 0: up, 1: single click, 2: double click,... */
	struct timeval tv;	/* timestamp on the last `up' event */
} buttonstate[MOUSE_MAXBUTTON];

/*
 * handle button click
 * Note that an ioctl is sent for each button
 */
static void
mouse_click(struct wscons_event *event)
{
	struct timeval max_date;
	struct timeval now;
	struct timeval delay;
	struct timezone tz;
	int i = event->value; /* button number */

	gettimeofday(&now, &tz);
	delay.tv_sec = mouse.clickthreshold / 1000;
	delay.tv_usec = (mouse.clickthreshold % 1000) * 1000;
	timersub(&now, &delay, &max_date);

	if (event->type == WSCONS_EVENT_MOUSE_DOWN) {
		if (timercmp(&max_date, &buttonstate[i].tv, >)) {
			buttonstate[i].tv.tv_sec = 0;
			buttonstate[i].tv.tv_usec = 0;
			buttonstate[i].count = 1;
		} else {
			buttonstate[i].count++;
		}
	} else {
		/* button is up */
		buttonstate[i].tv.tv_sec = now.tv_sec;
		buttonstate[i].tv.tv_usec = now.tv_usec;
	}

	/*
	 * we use the time field of wscons_event structure to put the number
	 * of multiple clicks
	 */
	if (event->type == WSCONS_EVENT_MOUSE_DOWN) {
		event->time.tv_sec = buttonstate[i].count;
		event->time.tv_nsec = 0;
	} else {
		/* button is up */
		event->time.tv_sec = 0;
		event->time.tv_nsec = 0;
	}
	ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
}

/* workaround for cursor speed on serial mice */
static void
normalize_event(struct wscons_event *event)
{
	int dx, dy;
	int two_power = 1;

/* 2: normal speed, 3: slower cursor, 1: faster cursor */
#define NORMALIZE_DIVISOR 3

	switch (event->type) {
	case WSCONS_EVENT_MOUSE_DELTA_X:
		dx = abs(event->value);
		while (dx > 2) {
			two_power++;
			dx = dx / 2;
		}
		event->value = event->value / (NORMALIZE_DIVISOR * two_power);
		break;
	case WSCONS_EVENT_MOUSE_DELTA_Y:
		two_power = 1;
		dy = abs(event->value);
		while (dy > 2) {
			two_power++;
			dy = dy / 2;
		}
		event->value = event->value / (NORMALIZE_DIVISOR * two_power);
		break;
	}
}

/* send a wscons_event to the kernel */
static int
treat_event(struct wscons_event *event)
{
	struct wscons_event mapped_event;

	if (IS_MOTION_EVENT(event->type)) {
		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
		return 1;
	} else if (IS_BUTTON_EVENT(event->type) &&
	    (uint)event->value < MOUSE_MAXBUTTON) {
		mouse_map(event, &mapped_event);
		mouse_click(&mapped_event);
		return 1;
	}
	if (event->type == WSCONS_EVENT_WSMOUSED_CLOSE)
		/* we have to close mouse fd */
		return 0;
	return 1;
}

/* split a full mouse event into multiples wscons events */
static void
split_event(mousestatus_t *act)
{
	struct wscons_event event;
	int button, i, mask;

	if (act->dx != 0) {
		event.type = WSCONS_EVENT_MOUSE_DELTA_X;
		event.value = act->dx;
		normalize_event(&event);
		treat_event(&event);
	}
	if (act->dy != 0) {
		event.type = WSCONS_EVENT_MOUSE_DELTA_Y;
		event.value = 0 - act->dy;
		normalize_event(&event);
		treat_event(&event);
	}
	if (act->dz != 0) {
		event.type = WSCONS_EVENT_MOUSE_DELTA_Z;
		event.value = act->dz;
		treat_event(&event);
	}
	if (act->dw != 0) {
		event.type = WSCONS_EVENT_MOUSE_DELTA_W;
		event.value = act->dw;
		treat_event(&event);
	}

	/* buttons state */
	mask = act->flags & MOUSE_BUTTONS;
	if (mask == 0)
		/* no button modified */
		return;

	button = MOUSE_BUTTON1DOWN;
	for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); i++) {
		if (mask & 1) {
			event.type = (act->button & button) ?
			    WSCONS_EVENT_MOUSE_DOWN : WSCONS_EVENT_MOUSE_UP;
			event.value = i;
			treat_event(&event);
		}
		button <<= 1;
		mask >>= 1;
	}
}

/* main function */
static void
wsmoused(void)
{
	mousestatus_t action;
	struct wscons_event event; /* original wscons_event */
	struct pollfd pfd[1];
	int res;
	u_char b;
	struct stat mdev_stat;

	/* initialization */

	event.type = WSCONS_EVENT_WSMOUSED_ON;
	if (mouse.proto == P_WSCONS) {
		/* get major and minor of mouse device */
		res = stat(mouse.portname, &mdev_stat);
		if (res != -1)
			event.value = mdev_stat.st_rdev;
		else
			event.value = 0;
	} else {
		/* X11 won't start when using wsmoused(8) with a serial mouse */
		event.value = 0;
	}

	/* notify kernel the start of wsmoused */
	res = ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event);
	if (res != 0) {
		/* the display driver has no getchar() method */
		logerr(1, "this display driver has no support for wsmoused(8)");
	}

	bzero(&action, sizeof(action));
	bzero(&event, sizeof(event));
	bzero(&buttonstate, sizeof(buttonstate));

	pfd[0].fd = mouse.mfd;
	pfd[0].events = POLLIN;

	/* process mouse data */
	for (;;) {
		if (poll(pfd, 1, INFTIM) <= 0)
			logwarn("failed to read from mouse");

		if (mouse.proto == P_WSCONS) {
			/* wsmouse supported mouse */
			read(mouse.mfd, &event, sizeof(event));
			res = treat_event(&event);
			if (!res) {
				/*
				 * close mouse device and sleep until
				 * the X server releases it
				 */

				struct wscons_event sleeping;
				unsigned int tries;

				/* restore mouse resolution to default value */
				res = WSMOUSE_RES_DEFAULT;
				ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);

				close(mouse.mfd);
				mouse.mfd = -1;

				/* sleep until X server releases mouse device */
				sleeping.type = WSCONS_EVENT_WSMOUSED_SLEEP;
				sleeping.value = 0;
				ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED,
				    &sleeping);

				/*
				 * Since the X server could still be running
				 * (e.g. when switching from the graphics
				 * screen to a virtual text console), it might
				 * not have freed the device yet.
				 *
				 * Try to open the device until it succeeds.
				 */
				tries = 0;
				for (;;) {
					if ((mouse.mfd = open(mouse.portname,
					    O_RDONLY | O_NONBLOCK, 0)) != -1)
						break;

					if (tries < 10) {
						tries++;
						sleep(1);
					} else {
						logwarn("unable to open %s, "
						    "will retry in 10 seconds",
						    mouse.portname);
						sleep(10);
					}
				}

				wsmouse_init();
			}
		} else {
			/* serial mouse (not supported by wsmouse) */
			res = read(mouse.mfd, &b, 1);

			/* if we have a full mouse event */
			if (mouse_protocol(b, &action))
				/* split it as multiple wscons_event */
				split_event(&action);
		}
	}
}


static void
usage(void)
{
	fprintf(stderr, "usage: %s [-2dfi] [-C thresh] [-D device] [-I file]"
	    " [-M N=M]\n\t[-p device] [-t type]\n", __progname);
	exit(1);
}

int
main(int argc, char **argv)
{
	FILE *fp;
	unsigned int type;
	int opt;
	int i;

#define GETOPT_STRING "2dfhip:t:C:D:I:M:"
	while ((opt = (getopt(argc, argv, GETOPT_STRING))) != -1) {
		switch (opt) {
		case '2':
			/* on two button mice, right button pastes */
			p2l[MOUSE_BUTTON3] = MOUSE_BUTTON2;
			break;
		case 'd':
			++debug;
			break;
		case 'f':
			nodaemon = TRUE;
			break;
		case 'h':
			usage();
			break;
		case 'i':
			identify = TRUE;
			nodaemon = TRUE;
			break;
		case 'p':
			if ((mouse.portname = strdup(optarg)) == NULL)
				logerr(1, "out of memory");
			break;
		case 't':
			if (strcmp(optarg, "auto") == 0) {
				mouse.proto = P_UNKNOWN;
				mouse.flags &= ~NoPnP;
				break;
			}
			for (i = 0; mouse_names[i] != NULL; i++)
				if (strcmp(optarg,mouse_names[i]) == 0) {
					mouse.proto = i;
					mouse.flags |= NoPnP;
					break;
				}
			if (mouse_names[i] != NULL)
				break;
			warnx("no such mouse protocol `%s'", optarg);
			usage();
			break;
		case 'C':
#define MAX_CLICKTHRESHOLD 2000 /* max delay for double click */
			mouse.clickthreshold = atoi(optarg);
			if (mouse.clickthreshold < 0 ||
			    mouse.clickthreshold > MAX_CLICKTHRESHOLD) {
				warnx("invalid threshold `%s': max value is %d",
				    optarg, MAX_CLICKTHRESHOLD);
				usage();
			}
			break;
		case 'D':
			if ((mouse.ttyname = strdup(optarg)) == NULL)
				logerr(1, "out of memory");
			break;
		case 'I':
			pidfile = optarg;
			break;
		case 'M':
			if (!mouse_installmap(optarg)) {
				warnx("invalid mapping `%s'", optarg);
				usage();
			}
			break;
		default:
			usage();
		}
	}

	/*
	 * Use defaults if unspecified
	 */
	if (mouse.portname == NULL)
		mouse.portname = WSMOUSE_DEV;
	if (mouse.ttyname == NULL)
		mouse.ttyname = DEFAULT_TTY;

	if (!nodaemon) {
		openlog(__progname, LOG_PID, LOG_DAEMON);
		if (daemon(0, 0)) {
			logerr(1, "failed to become a daemon");
		} else {
			background = TRUE;
			if (pidfile != NULL) {
				fp = fopen(pidfile, "w");
				if (fp != NULL) {
					fprintf(fp, "%ld\n", (long)getpid());
					fclose(fp);
				}
			}
		}
	}

	if (identify == FALSE) {
		if ((mouse.cfd = open(mouse.ttyname, O_RDWR, 0)) == -1)
			logerr(1, "cannot open %s", mouse.ttyname);
	}

	if ((mouse.mfd = open(mouse.portname,
	    O_RDONLY | O_NONBLOCK, 0)) == -1)
		logerr(1, "unable to open %s", mouse.portname);

	/*
	 * Find out whether the mouse device is a wsmouse device
	 * or a serial device.
	 */
	if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) != -1)
		mouse.proto = P_WSCONS;
	else {
		if (mouse_identify() == P_UNKNOWN) {
			close(mouse.mfd);
			logerr(1, "cannot determine mouse type on %s",
			    mouse.portname);
		}
	}

	if (identify == TRUE) {
		if (mouse.proto == P_WSCONS)
			wsmouse_identify();
		else
			printf("serial mouse: %s type\n",
			    mouse_name(mouse.proto));
		exit(0);
	}

	signal(SIGINT, terminate);
	signal(SIGQUIT, terminate);
	signal(SIGTERM, terminate);

	if (mouse.proto == P_WSCONS)
		wsmouse_init();
	else
		mouse_init();

	wsmoused();
	exit(0);
}