1 /* $OpenBSD: wsmoused.c,v 1.37 2017/10/24 09:36:13 jsg 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 161 ioctl(mouse.mfd, WSMOUSEIO_SRES, &res); 162 } 163 164 /* 165 * Buttons remapping 166 */ 167 168 /* physical to logical button mapping */ 169 static int p2l[MOUSE_MAXBUTTON] = { 170 MOUSE_BUTTON1, MOUSE_BUTTON2, MOUSE_BUTTON3, MOUSE_BUTTON4, 171 MOUSE_BUTTON5, MOUSE_BUTTON6, MOUSE_BUTTON7, MOUSE_BUTTON8, 172 }; 173 174 static char * 175 skipspace(char *s) 176 { 177 while (isspace((unsigned char)*s)) 178 ++s; 179 return s; 180 } 181 182 /* mouse_installmap : install a map between physical and logical buttons */ 183 static int 184 mouse_installmap(char *arg) 185 { 186 int pbutton; 187 int lbutton; 188 char *s; 189 190 while (*arg) { 191 arg = skipspace(arg); 192 s = arg; 193 while (isdigit((unsigned char)*arg)) 194 ++arg; 195 arg = skipspace(arg); 196 if ((arg <= s) || (*arg != '=')) 197 return FALSE; 198 lbutton = atoi(s); 199 200 arg = skipspace(++arg); 201 s = arg; 202 while (isdigit((unsigned char)*arg)) 203 ++arg; 204 if (arg <= s || (!isspace((unsigned char)*arg) && *arg != '\0')) 205 return FALSE; 206 pbutton = atoi(s); 207 208 if (lbutton <= 0 || lbutton > MOUSE_MAXBUTTON) 209 return FALSE; 210 if (pbutton <= 0 || pbutton > MOUSE_MAXBUTTON) 211 return FALSE; 212 p2l[pbutton - 1] = lbutton - 1; 213 } 214 return TRUE; 215 } 216 217 /* terminate signals handler */ 218 static void 219 terminate(int sig) 220 { 221 struct wscons_event event; 222 unsigned int res; 223 224 if (mouse.mfd != -1) { 225 event.type = WSCONS_EVENT_WSMOUSED_OFF; 226 ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event); 227 res = WSMOUSE_RES_DEFAULT; 228 ioctl(mouse.mfd, WSMOUSEIO_SRES, &res); 229 close(mouse.mfd); 230 mouse.mfd = -1; 231 } 232 _exit(0); 233 } 234 235 /* buttons status (for multiple click detection) */ 236 static struct { 237 int count; /* 0: up, 1: single click, 2: double click,... */ 238 struct timeval tv; /* timestamp on the last `up' event */ 239 } buttonstate[MOUSE_MAXBUTTON]; 240 241 /* 242 * handle button click 243 * Note that an ioctl is sent for each button 244 */ 245 static void 246 mouse_click(struct wscons_event *event) 247 { 248 struct timeval max_date; 249 struct timeval now; 250 struct timeval delay; 251 int i; /* button number */ 252 253 i = event->value = p2l[event->value]; 254 255 gettimeofday(&now, NULL); 256 delay.tv_sec = mouse.clickthreshold / 1000; 257 delay.tv_usec = (mouse.clickthreshold % 1000) * 1000; 258 timersub(&now, &delay, &max_date); 259 260 if (event->type == WSCONS_EVENT_MOUSE_DOWN) { 261 if (timercmp(&max_date, &buttonstate[i].tv, >)) { 262 timerclear(&buttonstate[i].tv); 263 buttonstate[i].count = 1; 264 } else { 265 buttonstate[i].count++; 266 } 267 } else { 268 /* button is up */ 269 buttonstate[i].tv.tv_sec = now.tv_sec; 270 buttonstate[i].tv.tv_usec = now.tv_usec; 271 } 272 273 /* 274 * we use the time field of wscons_event structure to put the number 275 * of multiple clicks 276 */ 277 if (event->type == WSCONS_EVENT_MOUSE_DOWN) { 278 event->time.tv_sec = buttonstate[i].count; 279 event->time.tv_nsec = 0; 280 } else { 281 /* button is up */ 282 event->time.tv_sec = 0; 283 event->time.tv_nsec = 0; 284 } 285 ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event); 286 } 287 288 /* workaround for cursor speed on serial mice */ 289 static void 290 normalize_event(struct wscons_event *event) 291 { 292 int dx, dy; 293 int two_power = 1; 294 295 /* 2: normal speed, 3: slower cursor, 1: faster cursor */ 296 #define NORMALIZE_DIVISOR 3 297 298 switch (event->type) { 299 case WSCONS_EVENT_MOUSE_DELTA_X: 300 dx = abs(event->value); 301 while (dx > 2) { 302 two_power++; 303 dx = dx / 2; 304 } 305 event->value = event->value / (NORMALIZE_DIVISOR * two_power); 306 break; 307 case WSCONS_EVENT_MOUSE_DELTA_Y: 308 two_power = 1; 309 dy = abs(event->value); 310 while (dy > 2) { 311 two_power++; 312 dy = dy / 2; 313 } 314 event->value = event->value / (NORMALIZE_DIVISOR * two_power); 315 break; 316 } 317 } 318 319 /* send a wscons_event to the kernel */ 320 static void 321 treat_event(struct wscons_event *event) 322 { 323 if (IS_MOTION_EVENT(event->type)) { 324 ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event); 325 } else if (IS_BUTTON_EVENT(event->type) && 326 (uint)event->value < MOUSE_MAXBUTTON) { 327 mouse_click(event); 328 } 329 } 330 331 /* split a full mouse event into multiples wscons events */ 332 static void 333 split_event(mousestatus_t *act) 334 { 335 struct wscons_event event; 336 int button, i, mask; 337 338 if (act->dx != 0) { 339 event.type = WSCONS_EVENT_MOUSE_DELTA_X; 340 event.value = act->dx; 341 normalize_event(&event); 342 treat_event(&event); 343 } 344 if (act->dy != 0) { 345 event.type = WSCONS_EVENT_MOUSE_DELTA_Y; 346 event.value = 0 - act->dy; 347 normalize_event(&event); 348 treat_event(&event); 349 } 350 if (act->dz != 0) { 351 event.type = WSCONS_EVENT_MOUSE_DELTA_Z; 352 event.value = act->dz; 353 treat_event(&event); 354 } 355 if (act->dw != 0) { 356 event.type = WSCONS_EVENT_MOUSE_DELTA_W; 357 event.value = act->dw; 358 treat_event(&event); 359 } 360 361 /* buttons state */ 362 mask = act->flags & MOUSE_BUTTONS; 363 if (mask == 0) 364 /* no button modified */ 365 return; 366 367 button = MOUSE_BUTTON1DOWN; 368 for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); i++) { 369 if (mask & 1) { 370 event.type = (act->button & button) ? 371 WSCONS_EVENT_MOUSE_DOWN : WSCONS_EVENT_MOUSE_UP; 372 event.value = i; 373 treat_event(&event); 374 } 375 button <<= 1; 376 mask >>= 1; 377 } 378 } 379 380 /* main function */ 381 static void 382 wsmoused(void) 383 { 384 mousestatus_t action; 385 struct wscons_event event; /* original wscons_event */ 386 struct pollfd pfd[1]; 387 int res; 388 u_char b; 389 390 /* notify kernel the start of wsmoused */ 391 event.type = WSCONS_EVENT_WSMOUSED_ON; 392 res = ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event); 393 if (res != 0) { 394 /* the display driver has no getchar() method */ 395 logerr(1, "this display driver has no support for wsmoused(8)"); 396 } 397 398 bzero(&action, sizeof(action)); 399 bzero(&event, sizeof(event)); 400 bzero(&buttonstate, sizeof(buttonstate)); 401 402 pfd[0].fd = mouse.mfd; 403 pfd[0].events = POLLIN; 404 405 /* process mouse data */ 406 for (;;) { 407 if (poll(pfd, 1, INFTIM) <= 0) 408 logwarn("failed to read from mouse"); 409 410 if (mouse.proto == P_WSCONS) { 411 /* wsmouse supported mouse */ 412 read(mouse.mfd, &event, sizeof(event)); 413 treat_event(&event); 414 } else { 415 /* serial mouse (not supported by wsmouse) */ 416 res = read(mouse.mfd, &b, 1); 417 418 /* if we have a full mouse event */ 419 if (mouse_protocol(b, &action)) 420 /* split it as multiple wscons_event */ 421 split_event(&action); 422 } 423 } 424 } 425 426 427 static void 428 usage(void) 429 { 430 fprintf(stderr, "usage: %s [-2dfi] [-C thresh] [-D device]" 431 " [-M N=M]\n\t[-p device] [-t type]\n", __progname); 432 exit(1); 433 } 434 435 int 436 main(int argc, char **argv) 437 { 438 unsigned int type; 439 int opt; 440 int i; 441 442 #define GETOPT_STRING "2dfhip:t:C:D:M:" 443 while ((opt = (getopt(argc, argv, GETOPT_STRING))) != -1) { 444 switch (opt) { 445 case '2': 446 /* on two button mice, right button pastes */ 447 p2l[MOUSE_BUTTON3] = MOUSE_BUTTON2; 448 break; 449 case 'd': 450 ++debug; 451 break; 452 case 'f': 453 nodaemon = TRUE; 454 break; 455 case 'h': 456 usage(); 457 break; 458 case 'i': 459 identify = TRUE; 460 nodaemon = TRUE; 461 break; 462 case 'p': 463 if ((mouse.portname = strdup(optarg)) == NULL) 464 logerr(1, "out of memory"); 465 break; 466 case 't': 467 if (strcmp(optarg, "auto") == 0) { 468 mouse.proto = P_UNKNOWN; 469 mouse.flags &= ~NoPnP; 470 break; 471 } 472 for (i = 0; mouse_names[i] != NULL; i++) 473 if (strcmp(optarg,mouse_names[i]) == 0) { 474 mouse.proto = i; 475 mouse.flags |= NoPnP; 476 break; 477 } 478 if (mouse_names[i] != NULL) 479 break; 480 warnx("no such mouse protocol `%s'", optarg); 481 usage(); 482 break; 483 case 'C': 484 #define MAX_CLICKTHRESHOLD 2000 /* max delay for double click */ 485 mouse.clickthreshold = atoi(optarg); 486 if (mouse.clickthreshold < 0 || 487 mouse.clickthreshold > MAX_CLICKTHRESHOLD) { 488 warnx("invalid threshold `%s': max value is %d", 489 optarg, MAX_CLICKTHRESHOLD); 490 usage(); 491 } 492 break; 493 case 'D': 494 if ((mouse.ttyname = strdup(optarg)) == NULL) 495 logerr(1, "out of memory"); 496 break; 497 case 'M': 498 if (!mouse_installmap(optarg)) { 499 warnx("invalid mapping `%s'", optarg); 500 usage(); 501 } 502 break; 503 default: 504 usage(); 505 } 506 } 507 508 /* 509 * Use defaults if unspecified 510 */ 511 if (mouse.portname == NULL) 512 mouse.portname = WSMOUSE_DEV; 513 if (mouse.ttyname == NULL) 514 mouse.ttyname = DEFAULT_TTY; 515 516 if (identify == FALSE) { 517 if ((mouse.cfd = open(mouse.ttyname, O_RDWR, 0)) == -1) 518 logerr(1, "cannot open %s", mouse.ttyname); 519 } 520 521 if ((mouse.mfd = open(mouse.portname, 522 O_RDONLY | O_NONBLOCK, 0)) == -1) 523 logerr(1, "unable to open %s", mouse.portname); 524 525 /* 526 * Find out whether the mouse device is a wsmouse device 527 * or a serial device. 528 */ 529 if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) != -1) 530 mouse.proto = P_WSCONS; 531 else { 532 if (mouse_identify() == P_UNKNOWN) { 533 close(mouse.mfd); 534 logerr(1, "cannot determine mouse type on %s", 535 mouse.portname); 536 } 537 } 538 539 if (identify == TRUE) { 540 if (mouse.proto == P_WSCONS) 541 wsmouse_identify(); 542 else 543 printf("serial mouse: %s type\n", 544 mouse_name(mouse.proto)); 545 exit(0); 546 } 547 548 signal(SIGINT, terminate); 549 signal(SIGQUIT, terminate); 550 signal(SIGTERM, terminate); 551 552 if (mouse.proto == P_WSCONS) 553 wsmouse_init(); 554 else 555 mouse_init(); 556 557 if (!nodaemon) { 558 openlog(__progname, LOG_PID, LOG_DAEMON); 559 if (daemon(0, 0)) { 560 logerr(1, "failed to become a daemon"); 561 } else { 562 background = TRUE; 563 } 564 } 565 566 wsmoused(); 567 exit(0); 568 } 569