xref: /openbsd/usr.sbin/wsmoused/wsmoused.c (revision ca198d9f)
1 /* $OpenBSD: wsmoused.c,v 1.15 2003/09/26 16:09:27 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 <setjmp.h>
66 #include <signal.h>
67 #include <poll.h>
68 #include <stdio.h>
69 #include <string.h>
70 #include <stdlib.h>
71 #include <syslog.h>
72 #include <varargs.h>
73 
74 #include "mouse_protocols.h"
75 #include "wsmoused.h"
76 
77 extern char *__progname;
78 extern char *mouse_names[];
79 
80 int debug = 0;
81 int nodaemon = FALSE;
82 int background = FALSE;
83 int identify = FALSE;
84 char *pidfile = "/var/run/wsmoused.pid";
85 
86 mouse_t mouse = {
87 	flags : 0,
88 	portname : NULL,
89 	proto : P_UNKNOWN,
90 	baudrate : 1200,
91 	old_baudrate : 1200,
92 	rate : MOUSE_RATE_UNKNOWN,
93 	resolution : MOUSE_RES_UNKNOWN,
94 	zmap : 0,
95 	wmode : 0,
96 	mfd : -1,
97 	clickthreshold : 500,	/* 0.5 sec */
98 };
99 
100 /* identify the type of a wsmouse supported mouse */
101 void
102 wsmouse_identify(void)
103 {
104 	unsigned int type;
105 
106 	if (mouse.mfd != -1) {
107 		if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) == -1)
108 			err(1, "can't detect mouse type");
109 		printf("wsmouse supported mouse: ");
110 		switch (type) {
111 		case WSMOUSE_TYPE_VSXXX:
112 			printf("DEC serial\n");
113 			break;
114 		case WSMOUSE_TYPE_PS2:
115 			printf("PS/2 compatible\n");
116 			break;
117 		case WSMOUSE_TYPE_USB:
118 			printf("USB\n");
119 			break;
120 		case WSMOUSE_TYPE_LMS:
121 			printf("Logitech busmouse\n");
122 			break;
123 		case WSMOUSE_TYPE_MMS:
124 			printf("Microsoft InPort mouse\n");
125 			break;
126 		case WSMOUSE_TYPE_TPANEL:
127 			printf("Generic Touch Panel\n");
128 			break;
129 		case WSMOUSE_TYPE_NEXT:
130 			printf("NeXT\n");
131 			break;
132 		case WSMOUSE_TYPE_ARCHIMEDES:
133 			printf("Archimedes\n");
134 			break;
135 		default:
136 			printf("Unknown\n");
137 			break;
138 		}
139 	} else
140 		warnx("unable to open %s", mouse.portname);
141 }
142 
143 /* wsmouse_init : init a wsmouse compatible mouse */
144 void
145 wsmouse_init(void)
146 {
147 	unsigned int res = WSMOUSE_RES_MIN;
148 	unsigned int rate = WSMOUSE_RATE_DEFAULT;
149 
150 	ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
151 	ioctl(mouse.mfd, WSMOUSEIO_SRATE, &rate);
152 }
153 
154 /*
155  * Buttons remapping
156  */
157 
158 /* physical to logical button mapping */
159 static int p2l[MOUSE_MAXBUTTON] = {
160 	MOUSE_BUTTON1,	MOUSE_BUTTON2,	MOUSE_BUTTON3,	MOUSE_BUTTON4,
161 	MOUSE_BUTTON5,	MOUSE_BUTTON6,	MOUSE_BUTTON7,	MOUSE_BUTTON8,
162 };
163 
164 static char *
165 skipspace(char *s)
166 {
167 	while (isspace(*s))
168 		++s;
169 	return s;
170 }
171 
172 /* mouse_installmap : install a map between physical and logical buttons */
173 static int
174 mouse_installmap(char *arg)
175 {
176 	int pbutton;
177 	int lbutton;
178 	char *s;
179 
180 	while (*arg) {
181 		arg = skipspace(arg);
182 		s = arg;
183 		while (isdigit(*arg))
184 			++arg;
185 		arg = skipspace(arg);
186 		if ((arg <= s) || (*arg != '='))
187 			return FALSE;
188 		lbutton = atoi(s);
189 
190 		arg = skipspace(++arg);
191 		s = arg;
192 		while (isdigit(*arg))
193 			++arg;
194 		if (arg <= s || (!isspace(*arg) && *arg != '\0'))
195 			return FALSE;
196 		pbutton = atoi(s);
197 
198 		if (lbutton <= 0 || lbutton > MOUSE_MAXBUTTON)
199 			return FALSE;
200 		if (pbutton <= 0 || pbutton > MOUSE_MAXBUTTON)
201 			return FALSE;
202 		p2l[pbutton - 1] = lbutton - 1;
203 	}
204 	return TRUE;
205 }
206 
207 /* mouse_map : converts physical buttons to logical buttons */
208 static void
209 mouse_map(struct wscons_event *orig, struct wscons_event *mapped)
210 {
211 	mapped->type = orig->type;
212 	mapped->value = p2l[orig->value];
213 }
214 
215 /* terminate signals handler */
216 static void
217 terminate(int sig)
218 {
219 	struct wscons_event event;
220 	unsigned int res;
221 
222 	if (mouse.mfd != -1) {
223 		event.type = WSCONS_EVENT_WSMOUSED_OFF;
224 		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event);
225 		res = WSMOUSE_RES_DEFAULT;
226 		ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
227 		close(mouse.mfd);
228 		mouse.mfd = -1;
229 	}
230 	unlink(pidfile);
231 	_exit(0);
232 }
233 
234 /* buttons status (for multiple click detection) */
235 static struct {
236 	int count;		/* 0: up, 1: single click, 2: double click,... */
237 	struct timeval tv;	/* timestamp on the last `up' event */
238 } buttonstate[MOUSE_MAXBUTTON];
239 
240 /*
241  * handle button click
242  * Note that an ioctl is sent for each button
243  */
244 static void
245 mouse_click(struct wscons_event *event)
246 {
247 	struct timeval max_date;
248 	struct timeval now;
249 	struct timeval delay;
250 	struct timezone tz;
251 	int i = event->value; /* button number */
252 
253 	gettimeofday(&now, &tz);
254 	delay.tv_sec = mouse.clickthreshold / 1000;
255 	delay.tv_usec = (mouse.clickthreshold % 1000) * 1000;
256 	timersub(&now, &delay, &max_date);
257 
258 	if (event->type == WSCONS_EVENT_MOUSE_DOWN) {
259 		if (timercmp(&max_date, &buttonstate[i].tv, >)) {
260 			buttonstate[i].tv.tv_sec = 0;
261 			buttonstate[i].tv.tv_usec = 0;
262 			buttonstate[i].count = 1;
263 		} else {
264 			buttonstate[i].count++;
265 		}
266 	} else {
267 		/* button is up */
268 		buttonstate[i].tv.tv_sec = now.tv_sec;
269 		buttonstate[i].tv.tv_usec = now.tv_usec;
270 	}
271 
272 	/*
273 	 * we use the time field of wscons_event structure to put the number
274 	 * of multiple clicks
275 	 */
276 	if (event->type == WSCONS_EVENT_MOUSE_DOWN) {
277 		event->time.tv_sec = buttonstate[i].count;
278 		event->time.tv_nsec = 0;
279 	} else {
280 		/* button is up */
281 		event->time.tv_sec = 0;
282 		event->time.tv_nsec = 0;
283 	}
284 	ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
285 }
286 
287 /* workaround for cursor speed on serial mice */
288 static void
289 normalize_event(struct wscons_event *event)
290 {
291 	int dx, dy;
292 	int two_power = 1;
293 
294 /* 2: normal speed, 3: slower cursor, 1: faster cursor */
295 #define NORMALIZE_DIVISOR 3
296 
297 	switch (event->type) {
298 	case WSCONS_EVENT_MOUSE_DELTA_X:
299 		dx = abs(event->value);
300 		while (dx > 2) {
301 			two_power++;
302 			dx = dx / 2;
303 		}
304 		event->value = event->value / (NORMALIZE_DIVISOR * two_power);
305 		break;
306 	case WSCONS_EVENT_MOUSE_DELTA_Y:
307 		two_power = 1;
308 		dy = abs(event->value);
309 		while (dy > 2) {
310 			two_power++;
311 			dy = dy / 2;
312 		}
313 		event->value = event->value / (NORMALIZE_DIVISOR * two_power);
314 		break;
315 	}
316 }
317 
318 /* send a wscons_event to the kernel */
319 static int
320 treat_event(struct wscons_event *event)
321 {
322 	struct wscons_event mapped_event;
323 
324 	if (IS_MOTION_EVENT(event->type)) {
325 		ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event);
326 		return 1;
327 	} else if (IS_BUTTON_EVENT(event->type)) {
328 		mouse_map(event, &mapped_event);
329 		mouse_click(&mapped_event);
330 		return 1;
331 	}
332 	if (event->type == WSCONS_EVENT_WSMOUSED_CLOSE)
333 		/* we have to close mouse fd */
334 		return 0;
335 	return 1;
336 }
337 
338 /* split a full mouse event into multiples wscons events */
339 static void
340 split_event(mousestatus_t *act)
341 {
342 	struct wscons_event event;
343 	int button, i, mask;
344 
345 	if (act->dx != 0) {
346 		event.type = WSCONS_EVENT_MOUSE_DELTA_X;
347 		event.value = act->dx;
348 		normalize_event(&event);
349 		treat_event(&event);
350 	}
351 	if (act->dy != 0) {
352 		event.type = WSCONS_EVENT_MOUSE_DELTA_Y;
353 		event.value = 0 - act->dy;
354 		normalize_event(&event);
355 		treat_event(&event);
356 	}
357 	if (act->dz != 0) {
358 		event.type = WSCONS_EVENT_MOUSE_DELTA_Z;
359 		event.value = act->dz;
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 	FILE *fp;
392 	struct stat mdev_stat;
393 
394 	if (!nodaemon && !background) {
395 		if (daemon(0, 0)) {
396 			logerr(1, "failed to become a daemon");
397 		} else {
398 			background = TRUE;
399 			fp = fopen(pidfile, "w");
400 			if (fp != NULL) {
401 				fprintf(fp, "%ld\n", (long)getpid());
402 				fclose(fp);
403 			}
404 		}
405 	}
406 
407 	if ((mouse.cfd = open("/dev/ttyCcfg", O_RDWR, 0)) == -1)
408 		logerr(1, "cannot open /dev/ttyCcfg");
409 
410 	/* initialization */
411 
412 	event.type = WSCONS_EVENT_WSMOUSED_ON;
413 	if (IS_WSMOUSE_DEV(mouse.portname)) {
414 
415 		/* get major and minor of mouse device */
416 		res = stat(mouse.portname, &mdev_stat);
417 		if (res != -1)
418 			event.value = mdev_stat.st_rdev;
419 		else
420 			event.value = 0;
421 	}
422 	else
423 		/* X11 won't start using wsmoused(8) with a serial mouse */
424 		event.value = 0;
425 
426 	/* notify kernel to start wsmoused */
427 	res = ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event);
428 	if (res != 0) {
429 		/* the display driver has no getchar() method */
430 		logerr(1, "this display driver has no support for wsmoused(8)");
431 	}
432 
433 	bzero(&action, sizeof(action));
434 	bzero(&event, sizeof(event));
435 	bzero(&buttonstate, sizeof(buttonstate));
436 
437 	pfd[0].fd = mouse.mfd;
438 	pfd[0].events = POLLIN;
439 
440 	/* process mouse data */
441 	for (;;) {
442 		if (poll(pfd, 1, INFTIM) <= 0)
443 			logwarn("failed to read from mouse");
444 		if (IS_WSMOUSE_DEV(mouse.portname)) {
445 			/* wsmouse supported mouse */
446 			read(mouse.mfd, &event, sizeof(event));
447 			res = treat_event(&event);
448 			if (!res) {
449 				/* close mouse device and sleep until
450 				   the X server release it */
451 
452 				struct wscons_event sleeping;
453 
454 				/* restore mouse resolution to default value */
455 				res = WSMOUSE_RES_DEFAULT;
456 				ioctl(mouse.mfd, WSMOUSEIO_SRES, &res);
457 
458 				close(mouse.mfd);
459 				mouse.mfd = -1;
460 
461 				/* sleep until X server releases mouse device */
462 				sleeping.type = WSCONS_EVENT_WSMOUSED_SLEEP;
463 				sleeping.value = 0;
464 				ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED,
465 				    &sleeping);
466 
467 				/* waiting for availability of mouse device */
468 				sleep(1);
469 
470 				if ((mouse.mfd = open(mouse.portname,
471 				    O_RDONLY | O_NONBLOCK, 0)) == -1)
472 					logerr(1, "unable to open %s",
473 					    mouse.portname);
474 				mouse_init();
475 			}
476 		} else {
477 			/* serial mouse (not supported by wsmouse) */
478 			res = read(mouse.mfd, &b, 1);
479 
480 			/* if we have a full mouse event */
481 			if (mouse_protocol(b, &action))
482 				/* split it as multiple wscons_event */
483 				split_event(&action);
484 		}
485 	}
486 }
487 
488 
489 static void
490 usage(void)
491 {
492 	fprintf(stderr, "usage: %s [-2df] [-t protocol] [-C threshold] [-I file] \
493 [-M N=M] [-p port]", __progname);
494 	fprintf(stderr, "       %s -i [-p port]\n", __progname);
495 	exit(1);
496 }
497 
498 int
499 main(int argc, char **argv)
500 {
501 	int opt;
502 	int i;
503 
504 #define GETOPT_STRING "2dfhip:t:C:I:M:"
505 	while ((opt = (getopt(argc, argv, GETOPT_STRING))) != -1) {
506 		switch (opt) {
507 		case '2':
508 			/* on two button mice, right button pastes */
509 			p2l[MOUSE_BUTTON3] = MOUSE_BUTTON2;
510 			break;
511 		case 'd':
512 			++debug;
513 			break;
514 		case 'f':
515 			nodaemon = TRUE;
516 			break;
517 		case 'h':
518 			usage();
519 			break;
520 		case 'i':
521 			identify = TRUE;
522 			break;
523 		case 'p':
524 			if ((mouse.portname = strdup(optarg)) == NULL)
525 				logerr(1, "out of memory");
526 			break;
527 		case 't':
528 			if (strcmp(optarg, "auto") == 0) {
529 				mouse.proto = P_UNKNOWN;
530 				mouse.flags &= ~NoPnP;
531 				break;
532 			}
533 			for (i = 0; mouse_names[i] != NULL; i++)
534 				if (strcmp(optarg,mouse_names[i]) == 0) {
535 					mouse.proto = i;
536 					mouse.flags |= NoPnP;
537 					break;
538 				}
539 			if (mouse_names[i] != NULL)
540 				break;
541 			warnx("no such mouse protocol `%s'", optarg);
542 			usage();
543 			break;
544 		case 'C':
545 #define MAX_CLICKTHRESHOLD 2000 /* max delay for double click */
546 			mouse.clickthreshold = atoi(optarg);
547 			if (mouse.clickthreshold < 0 ||
548 			    mouse.clickthreshold > MAX_CLICKTHRESHOLD) {
549 				warnx("invalid threshold `%s': max value is %d",
550 				    optarg, MAX_CLICKTHRESHOLD);
551 				usage();
552 			}
553 			break;
554 		case 'I':
555 			pidfile = optarg;
556 			break;
557 		case 'M':
558 			if (!mouse_installmap(optarg)) {
559 				warnx("invalid mapping `%s'", optarg);
560 				usage();
561 			}
562 			break;
563 		default:
564 			usage();
565 		}
566 	}
567 	if (mouse.portname == NULL)
568 		/* default is /dev/wsmouse */
569 		mouse.portname = WSMOUSE_DEV;
570 
571 	if (!nodaemon)
572 		openlog(__progname, LOG_PID, LOG_DAEMON);
573 
574 	for (;;) {
575 		signal(SIGINT , terminate);
576 		signal(SIGQUIT, terminate);
577 		signal(SIGTERM, terminate);
578 		signal(SIGKILL, terminate);
579 		if ((mouse.mfd = open(mouse.portname,
580 		    O_RDONLY | O_NONBLOCK, 0)) == -1)
581 			logerr(1, "unable to open %s", mouse.portname);
582 		if (IS_SERIAL_DEV(mouse.portname)) {
583 			if (mouse_identify() == P_UNKNOWN) {
584 				close(mouse.mfd);
585 				logerr(1, "cannot determine mouse type on %s",
586 				    mouse.portname);
587 			}
588 		}
589 
590 		if (identify == TRUE) {
591 			if (IS_WSMOUSE_DEV(mouse.portname))
592 				wsmouse_identify();
593 			else
594 				printf("serial mouse: %s type\n",
595 				    (char *)mouse_name(mouse.proto));
596 			exit(0);
597 		}
598 
599 		mouse_init();
600 		wsmoused();
601 	}
602 }
603