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