[BACK]Return to wsmoused.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / wsmoused

File: [local] / src / usr.sbin / wsmoused / wsmoused.c (download)

Revision 1.38, Sun Oct 24 21:24:19 2021 UTC (2 years, 7 months ago) by deraadt
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, HEAD
Changes since 1.37: +3 -3 lines

For open/openat, if the flags parameter does not contain O_CREAT, the
3rd (variadic) mode_t parameter is irrelevant.  Many developers in the past
have passed mode_t (0, 044, 0644, or such), which might lead future people
to copy this broken idiom, and perhaps even believe this parameter has some
meaning or implication or application. Delete them all.
This comes out of a conversation where tb@ noticed that a strange (but
intentional) pledge behaviour is to always knock-out high-bits from
mode_t on a number of system calls as a safety factor, and his bewilderment
that this appeared to be happening against valid modes (at least visually),
but no sorry, they are all irrelevant junk.  They could all be 0xdeafbeef.
ok millert

/* $OpenBSD: wsmoused.c,v 1.38 2021/10/24 21:24:19 deraadt 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"

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

int debug = 0;
int background = FALSE;
int nodaemon = FALSE;
int identify = FALSE;

mouse_t mouse = {
	.flags = 0,
	.portname = NULL,
	.ttyname = NULL,
	.proto = P_UNKNOWN,
	.rate = MOUSE_RATE_UNKNOWN,
	.resolution = MOUSE_RES_UNKNOWN,
	.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;

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

/*
 * 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((unsigned char)*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((unsigned char)*arg))
			++arg;
		arg = skipspace(arg);
		if ((arg <= s) || (*arg != '='))
			return FALSE;
		lbutton = atoi(s);

		arg = skipspace(++arg);
		s = arg;
		while (isdigit((unsigned char)*arg))
			++arg;
		if (arg <= s || (!isspace((unsigned char)*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;
}

/* 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;
	}
	_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;
	int i; /* button number */
	
	i = event->value = p2l[event->value];

	gettimeofday(&now, NULL);
	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, >)) {
			timerclear(&buttonstate[i].tv);
			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 void
treat_event(struct wscons_event *event)
{
	if (IS_MOTION_EVENT(event->type)) {
		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
	} else if (IS_BUTTON_EVENT(event->type) &&
	    (uint)event->value < MOUSE_MAXBUTTON) {
		mouse_click(event);
	}
}

/* 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;

	/* notify kernel the start of wsmoused */
	event.type = WSCONS_EVENT_WSMOUSED_ON;
	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));
			treat_event(&event);
		} 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]"
	    " [-M N=M]\n\t[-p device] [-t type]\n", __progname);
	exit(1);
}

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

#define GETOPT_STRING "2dfhip:t:C:D: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 '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 (identify == FALSE) {
		if ((mouse.cfd = open(mouse.ttyname, O_RDWR)) == -1)
			logerr(1, "cannot open %s", mouse.ttyname);
	}

	if ((mouse.mfd = open(mouse.portname,
	    O_RDONLY | O_NONBLOCK)) == -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();

	if (!nodaemon) {
		openlog(__progname, LOG_PID, LOG_DAEMON);
		if (daemon(0, 0)) {
			logerr(1, "failed to become a daemon");
		} else {
			background = TRUE;
		}
	}

	wsmoused();
	exit(0);
}