xref: /openbsd-src/usr.sbin/wsmoused/wsmoused.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /* $OpenBSD: wsmoused.c,v 1.36 2015/10/26 09:58:18 deraadt Exp $ */
2 
3 /*
4  * Copyright (c) 2001 Jean-Baptiste Marchand, Julien Montagne and Jerome Verdon
5  *
6  * Copyright (c) 1998 by Kazutaka Yokota
7  *
8  * Copyright (c) 1995 Michael Smith
9  *
10  * Copyright (c) 1993 by David Dawes <dawes@xfree86.org>
11  *
12  * Copyright (c) 1990,91 by Thomas Roell, Dinkelscherben, Germany.
13  *
14  * All rights reserved.
15  *
16  * Most of this code was taken from the FreeBSD moused daemon, written by
17  * Michael Smith. The FreeBSD moused daemon already contained code from the
18  * Xfree Project, written by David Dawes and Thomas Roell and Kazutaka Yokota.
19  *
20  * Adaptation to OpenBSD was done by Jean-Baptiste Marchand, Julien Montagne
21  * and Jerome Verdon.
22  *
23  * Redistribution and use in source and binary forms, with or without
24  * modification, are permitted provided that the following conditions
25  * are met:
26  * 1. Redistributions of source code must retain the above copyright
27  *    notice, this list of conditions and the following disclaimer.
28  * 2. Redistributions in binary form must reproduce the above copyright
29  *    notice, this list of conditions and the following disclaimer in the
30  *    documentation and/or other materials provided with the distribution.
31  * 3. All advertising materials mentioning features or use of this software
32  *    must display the following acknowledgement:
33  *	This product includes software developed by
34  *      David Dawes, Jean-Baptiste Marchand, Julien Montagne, Thomas Roell,
35  *      Michael Smith, Jerome Verdon and Kazutaka Yokota.
36  * 4. The name authors may not be used to endorse or promote products
37  *    derived from this software without specific prior written permission.
38  *
39  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
40  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
42  * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
43  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
45  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
46  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
47  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
48  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49  *
50  *
51  */
52 
53 #include <sys/ioctl.h>
54 #include <sys/stat.h>
55 #include <sys/types.h>
56 #include <sys/time.h>
57 #include <sys/tty.h>
58 #include <dev/wscons/wsconsio.h>
59 
60 #include <ctype.h>
61 #include <err.h>
62 #include <errno.h>
63 #include <fcntl.h>
64 #include <unistd.h>
65 #include <signal.h>
66 #include <poll.h>
67 #include <stdio.h>
68 #include <string.h>
69 #include <stdlib.h>
70 #include <syslog.h>
71 
72 #include "mouse_protocols.h"
73 #include "wsmoused.h"
74 
75 #define	DEFAULT_TTY	"/dev/ttyCcfg"
76 
77 extern char *__progname;
78 extern char *mouse_names[];
79 
80 int debug = 0;
81 int background = FALSE;
82 int nodaemon = FALSE;
83 int identify = FALSE;
84 
85 mouse_t mouse = {
86 	.flags = 0,
87 	.portname = NULL,
88 	.ttyname = NULL,
89 	.proto = P_UNKNOWN,
90 	.rate = MOUSE_RATE_UNKNOWN,
91 	.resolution = MOUSE_RES_UNKNOWN,
92 	.mfd = -1,
93 	.clickthreshold = 500,	/* 0.5 sec */
94 };
95 
96 /* identify the type of a wsmouse supported mouse */
97 void
98 wsmouse_identify(void)
99 {
100 	unsigned int type;
101 
102 	if (mouse.mfd != -1) {
103 		if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) == -1)
104 			err(1, "can't detect mouse type");
105 
106 		printf("wsmouse supported mouse: ");
107 		switch (type) {
108 		case WSMOUSE_TYPE_VSXXX:
109 			printf("DEC serial\n");
110 			break;
111 		case WSMOUSE_TYPE_PS2:
112 			printf("PS/2 compatible\n");
113 			break;
114 		case WSMOUSE_TYPE_USB:
115 			printf("USB\n");
116 			break;
117 		case WSMOUSE_TYPE_LMS:
118 			printf("Logitech busmouse\n");
119 			break;
120 		case WSMOUSE_TYPE_MMS:
121 			printf("Microsoft InPort mouse\n");
122 			break;
123 		case WSMOUSE_TYPE_TPANEL:
124 			printf("Generic Touch Panel\n");
125 			break;
126 		case WSMOUSE_TYPE_NEXT:
127 			printf("NeXT\n");
128 			break;
129 		case WSMOUSE_TYPE_ARCHIMEDES:
130 			printf("Archimedes\n");
131 			break;
132 		case WSMOUSE_TYPE_ADB:
133 			printf("ADB\n");
134 			break;
135 		case WSMOUSE_TYPE_HIL:
136 			printf("HP-HIL\n");
137 			break;
138 		case WSMOUSE_TYPE_LUNA:
139 			printf("Omron Luna\n");
140 			break;
141 		case WSMOUSE_TYPE_DOMAIN:
142 			printf("Apollo Domain\n");
143 			break;
144 		case WSMOUSE_TYPE_SUN:
145 			printf("Sun\n");
146 			break;
147 		default:
148 			printf("Unknown\n");
149 			break;
150 		}
151 	} else
152 		warnx("unable to open %s", mouse.portname);
153 }
154 
155 /* wsmouse_init : init a wsmouse compatible mouse */
156 void
157 wsmouse_init(void)
158 {
159 	unsigned int res = WSMOUSE_RES_MIN;
160 	unsigned int rate = WSMOUSE_RATE_DEFAULT;
161 
162 	ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
163 	ioctl(mouse.mfd, WSMOUSEIO_SRATE, &rate);
164 }
165 
166 /*
167  * Buttons remapping
168  */
169 
170 /* physical to logical button mapping */
171 static int p2l[MOUSE_MAXBUTTON] = {
172 	MOUSE_BUTTON1,	MOUSE_BUTTON2,	MOUSE_BUTTON3,	MOUSE_BUTTON4,
173 	MOUSE_BUTTON5,	MOUSE_BUTTON6,	MOUSE_BUTTON7,	MOUSE_BUTTON8,
174 };
175 
176 static char *
177 skipspace(char *s)
178 {
179 	while (isspace((unsigned char)*s))
180 		++s;
181 	return s;
182 }
183 
184 /* mouse_installmap : install a map between physical and logical buttons */
185 static int
186 mouse_installmap(char *arg)
187 {
188 	int pbutton;
189 	int lbutton;
190 	char *s;
191 
192 	while (*arg) {
193 		arg = skipspace(arg);
194 		s = arg;
195 		while (isdigit((unsigned char)*arg))
196 			++arg;
197 		arg = skipspace(arg);
198 		if ((arg <= s) || (*arg != '='))
199 			return FALSE;
200 		lbutton = atoi(s);
201 
202 		arg = skipspace(++arg);
203 		s = arg;
204 		while (isdigit((unsigned char)*arg))
205 			++arg;
206 		if (arg <= s || (!isspace((unsigned char)*arg) && *arg != '\0'))
207 			return FALSE;
208 		pbutton = atoi(s);
209 
210 		if (lbutton <= 0 || lbutton > MOUSE_MAXBUTTON)
211 			return FALSE;
212 		if (pbutton <= 0 || pbutton > MOUSE_MAXBUTTON)
213 			return FALSE;
214 		p2l[pbutton - 1] = lbutton - 1;
215 	}
216 	return TRUE;
217 }
218 
219 /* terminate signals handler */
220 static void
221 terminate(int sig)
222 {
223 	struct wscons_event event;
224 	unsigned int res;
225 
226 	if (mouse.mfd != -1) {
227 		event.type = WSCONS_EVENT_WSMOUSED_OFF;
228 		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event);
229 		res = WSMOUSE_RES_DEFAULT;
230 		ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
231 		close(mouse.mfd);
232 		mouse.mfd = -1;
233 	}
234 	_exit(0);
235 }
236 
237 /* buttons status (for multiple click detection) */
238 static struct {
239 	int count;		/* 0: up, 1: single click, 2: double click,... */
240 	struct timeval tv;	/* timestamp on the last `up' event */
241 } buttonstate[MOUSE_MAXBUTTON];
242 
243 /*
244  * handle button click
245  * Note that an ioctl is sent for each button
246  */
247 static void
248 mouse_click(struct wscons_event *event)
249 {
250 	struct timeval max_date;
251 	struct timeval now;
252 	struct timeval delay;
253 	int i; /* button number */
254 
255 	i = event->value = p2l[event->value];
256 
257 	gettimeofday(&now, NULL);
258 	delay.tv_sec = mouse.clickthreshold / 1000;
259 	delay.tv_usec = (mouse.clickthreshold % 1000) * 1000;
260 	timersub(&now, &delay, &max_date);
261 
262 	if (event->type == WSCONS_EVENT_MOUSE_DOWN) {
263 		if (timercmp(&max_date, &buttonstate[i].tv, >)) {
264 			timerclear(&buttonstate[i].tv);
265 			buttonstate[i].count = 1;
266 		} else {
267 			buttonstate[i].count++;
268 		}
269 	} else {
270 		/* button is up */
271 		buttonstate[i].tv.tv_sec = now.tv_sec;
272 		buttonstate[i].tv.tv_usec = now.tv_usec;
273 	}
274 
275 	/*
276 	 * we use the time field of wscons_event structure to put the number
277 	 * of multiple clicks
278 	 */
279 	if (event->type == WSCONS_EVENT_MOUSE_DOWN) {
280 		event->time.tv_sec = buttonstate[i].count;
281 		event->time.tv_nsec = 0;
282 	} else {
283 		/* button is up */
284 		event->time.tv_sec = 0;
285 		event->time.tv_nsec = 0;
286 	}
287 	ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
288 }
289 
290 /* workaround for cursor speed on serial mice */
291 static void
292 normalize_event(struct wscons_event *event)
293 {
294 	int dx, dy;
295 	int two_power = 1;
296 
297 /* 2: normal speed, 3: slower cursor, 1: faster cursor */
298 #define NORMALIZE_DIVISOR 3
299 
300 	switch (event->type) {
301 	case WSCONS_EVENT_MOUSE_DELTA_X:
302 		dx = abs(event->value);
303 		while (dx > 2) {
304 			two_power++;
305 			dx = dx / 2;
306 		}
307 		event->value = event->value / (NORMALIZE_DIVISOR * two_power);
308 		break;
309 	case WSCONS_EVENT_MOUSE_DELTA_Y:
310 		two_power = 1;
311 		dy = abs(event->value);
312 		while (dy > 2) {
313 			two_power++;
314 			dy = dy / 2;
315 		}
316 		event->value = event->value / (NORMALIZE_DIVISOR * two_power);
317 		break;
318 	}
319 }
320 
321 /* send a wscons_event to the kernel */
322 static void
323 treat_event(struct wscons_event *event)
324 {
325 	if (IS_MOTION_EVENT(event->type)) {
326 		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
327 	} else if (IS_BUTTON_EVENT(event->type) &&
328 	    (uint)event->value < MOUSE_MAXBUTTON) {
329 		mouse_click(event);
330 	}
331 }
332 
333 /* split a full mouse event into multiples wscons events */
334 static void
335 split_event(mousestatus_t *act)
336 {
337 	struct wscons_event event;
338 	int button, i, mask;
339 
340 	if (act->dx != 0) {
341 		event.type = WSCONS_EVENT_MOUSE_DELTA_X;
342 		event.value = act->dx;
343 		normalize_event(&event);
344 		treat_event(&event);
345 	}
346 	if (act->dy != 0) {
347 		event.type = WSCONS_EVENT_MOUSE_DELTA_Y;
348 		event.value = 0 - act->dy;
349 		normalize_event(&event);
350 		treat_event(&event);
351 	}
352 	if (act->dz != 0) {
353 		event.type = WSCONS_EVENT_MOUSE_DELTA_Z;
354 		event.value = act->dz;
355 		treat_event(&event);
356 	}
357 	if (act->dw != 0) {
358 		event.type = WSCONS_EVENT_MOUSE_DELTA_W;
359 		event.value = act->dw;
360 		treat_event(&event);
361 	}
362 
363 	/* buttons state */
364 	mask = act->flags & MOUSE_BUTTONS;
365 	if (mask == 0)
366 		/* no button modified */
367 		return;
368 
369 	button = MOUSE_BUTTON1DOWN;
370 	for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); i++) {
371 		if (mask & 1) {
372 			event.type = (act->button & button) ?
373 			    WSCONS_EVENT_MOUSE_DOWN : WSCONS_EVENT_MOUSE_UP;
374 			event.value = i;
375 			treat_event(&event);
376 		}
377 		button <<= 1;
378 		mask >>= 1;
379 	}
380 }
381 
382 /* main function */
383 static void
384 wsmoused(void)
385 {
386 	mousestatus_t action;
387 	struct wscons_event event; /* original wscons_event */
388 	struct pollfd pfd[1];
389 	int res;
390 	u_char b;
391 
392 	/* notify kernel the start of wsmoused */
393 	event.type = WSCONS_EVENT_WSMOUSED_ON;
394 	res = ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event);
395 	if (res != 0) {
396 		/* the display driver has no getchar() method */
397 		logerr(1, "this display driver has no support for wsmoused(8)");
398 	}
399 
400 	bzero(&action, sizeof(action));
401 	bzero(&event, sizeof(event));
402 	bzero(&buttonstate, sizeof(buttonstate));
403 
404 	pfd[0].fd = mouse.mfd;
405 	pfd[0].events = POLLIN;
406 
407 	/* process mouse data */
408 	for (;;) {
409 		if (poll(pfd, 1, INFTIM) <= 0)
410 			logwarn("failed to read from mouse");
411 
412 		if (mouse.proto == P_WSCONS) {
413 			/* wsmouse supported mouse */
414 			read(mouse.mfd, &event, sizeof(event));
415 			treat_event(&event);
416 		} else {
417 			/* serial mouse (not supported by wsmouse) */
418 			res = read(mouse.mfd, &b, 1);
419 
420 			/* if we have a full mouse event */
421 			if (mouse_protocol(b, &action))
422 				/* split it as multiple wscons_event */
423 				split_event(&action);
424 		}
425 	}
426 }
427 
428 
429 static void
430 usage(void)
431 {
432 	fprintf(stderr, "usage: %s [-2dfi] [-C thresh] [-D device]"
433 	    " [-M N=M]\n\t[-p device] [-t type]\n", __progname);
434 	exit(1);
435 }
436 
437 int
438 main(int argc, char **argv)
439 {
440 	unsigned int type;
441 	int opt;
442 	int i;
443 
444 #define GETOPT_STRING "2dfhip:t:C:D:M:"
445 	while ((opt = (getopt(argc, argv, GETOPT_STRING))) != -1) {
446 		switch (opt) {
447 		case '2':
448 			/* on two button mice, right button pastes */
449 			p2l[MOUSE_BUTTON3] = MOUSE_BUTTON2;
450 			break;
451 		case 'd':
452 			++debug;
453 			break;
454 		case 'f':
455 			nodaemon = TRUE;
456 			break;
457 		case 'h':
458 			usage();
459 			break;
460 		case 'i':
461 			identify = TRUE;
462 			nodaemon = TRUE;
463 			break;
464 		case 'p':
465 			if ((mouse.portname = strdup(optarg)) == NULL)
466 				logerr(1, "out of memory");
467 			break;
468 		case 't':
469 			if (strcmp(optarg, "auto") == 0) {
470 				mouse.proto = P_UNKNOWN;
471 				mouse.flags &= ~NoPnP;
472 				break;
473 			}
474 			for (i = 0; mouse_names[i] != NULL; i++)
475 				if (strcmp(optarg,mouse_names[i]) == 0) {
476 					mouse.proto = i;
477 					mouse.flags |= NoPnP;
478 					break;
479 				}
480 			if (mouse_names[i] != NULL)
481 				break;
482 			warnx("no such mouse protocol `%s'", optarg);
483 			usage();
484 			break;
485 		case 'C':
486 #define MAX_CLICKTHRESHOLD 2000 /* max delay for double click */
487 			mouse.clickthreshold = atoi(optarg);
488 			if (mouse.clickthreshold < 0 ||
489 			    mouse.clickthreshold > MAX_CLICKTHRESHOLD) {
490 				warnx("invalid threshold `%s': max value is %d",
491 				    optarg, MAX_CLICKTHRESHOLD);
492 				usage();
493 			}
494 			break;
495 		case 'D':
496 			if ((mouse.ttyname = strdup(optarg)) == NULL)
497 				logerr(1, "out of memory");
498 			break;
499 		case 'M':
500 			if (!mouse_installmap(optarg)) {
501 				warnx("invalid mapping `%s'", optarg);
502 				usage();
503 			}
504 			break;
505 		default:
506 			usage();
507 		}
508 	}
509 
510 	/*
511 	 * Use defaults if unspecified
512 	 */
513 	if (mouse.portname == NULL)
514 		mouse.portname = WSMOUSE_DEV;
515 	if (mouse.ttyname == NULL)
516 		mouse.ttyname = DEFAULT_TTY;
517 
518 	if (identify == FALSE) {
519 		if ((mouse.cfd = open(mouse.ttyname, O_RDWR, 0)) == -1)
520 			logerr(1, "cannot open %s", mouse.ttyname);
521 	}
522 
523 	if ((mouse.mfd = open(mouse.portname,
524 	    O_RDONLY | O_NONBLOCK, 0)) == -1)
525 		logerr(1, "unable to open %s", mouse.portname);
526 
527 	/*
528 	 * Find out whether the mouse device is a wsmouse device
529 	 * or a serial device.
530 	 */
531 	if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) != -1)
532 		mouse.proto = P_WSCONS;
533 	else {
534 		if (mouse_identify() == P_UNKNOWN) {
535 			close(mouse.mfd);
536 			logerr(1, "cannot determine mouse type on %s",
537 			    mouse.portname);
538 		}
539 	}
540 
541 	if (identify == TRUE) {
542 		if (mouse.proto == P_WSCONS)
543 			wsmouse_identify();
544 		else
545 			printf("serial mouse: %s type\n",
546 			    mouse_name(mouse.proto));
547 		exit(0);
548 	}
549 
550 	signal(SIGINT, terminate);
551 	signal(SIGQUIT, terminate);
552 	signal(SIGTERM, terminate);
553 
554 	if (mouse.proto == P_WSCONS)
555 		wsmouse_init();
556 	else
557 		mouse_init();
558 
559 	if (!nodaemon) {
560 		openlog(__progname, LOG_PID, LOG_DAEMON);
561 		if (daemon(0, 0)) {
562 			logerr(1, "failed to become a daemon");
563 		} else {
564 			background = TRUE;
565 		}
566 	}
567 
568 	wsmoused();
569 	exit(0);
570 }
571