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