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