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