/* $OpenBSD: wsmoused.c,v 1.37 2017/10/24 09:36:13 jsg Exp $ */ /* * Copyright (c) 2001 Jean-Baptiste Marchand, Julien Montagne and Jerome Verdon * * Copyright (c) 1998 by Kazutaka Yokota * * Copyright (c) 1995 Michael Smith * * Copyright (c) 1993 by David Dawes * * Copyright (c) 1990,91 by Thomas Roell, Dinkelscherben, Germany. * * All rights reserved. * * Most of this code was taken from the FreeBSD moused daemon, written by * Michael Smith. The FreeBSD moused daemon already contained code from the * Xfree Project, written by David Dawes and Thomas Roell and Kazutaka Yokota. * * Adaptation to OpenBSD was done by Jean-Baptiste Marchand, Julien Montagne * and Jerome Verdon. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by * David Dawes, Jean-Baptiste Marchand, Julien Montagne, Thomas Roell, * Michael Smith, Jerome Verdon and Kazutaka Yokota. * 4. The name authors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mouse_protocols.h" #include "wsmoused.h" #define DEFAULT_TTY "/dev/ttyCcfg" extern char *__progname; extern char *mouse_names[]; int debug = 0; int background = FALSE; int nodaemon = FALSE; int identify = FALSE; mouse_t mouse = { .flags = 0, .portname = NULL, .ttyname = NULL, .proto = P_UNKNOWN, .rate = MOUSE_RATE_UNKNOWN, .resolution = MOUSE_RES_UNKNOWN, .mfd = -1, .clickthreshold = 500, /* 0.5 sec */ }; /* identify the type of a wsmouse supported mouse */ void wsmouse_identify(void) { unsigned int type; if (mouse.mfd != -1) { if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) == -1) err(1, "can't detect mouse type"); printf("wsmouse supported mouse: "); switch (type) { case WSMOUSE_TYPE_VSXXX: printf("DEC serial\n"); break; case WSMOUSE_TYPE_PS2: printf("PS/2 compatible\n"); break; case WSMOUSE_TYPE_USB: printf("USB\n"); break; case WSMOUSE_TYPE_LMS: printf("Logitech busmouse\n"); break; case WSMOUSE_TYPE_MMS: printf("Microsoft InPort mouse\n"); break; case WSMOUSE_TYPE_TPANEL: printf("Generic Touch Panel\n"); break; case WSMOUSE_TYPE_NEXT: printf("NeXT\n"); break; case WSMOUSE_TYPE_ARCHIMEDES: printf("Archimedes\n"); break; case WSMOUSE_TYPE_ADB: printf("ADB\n"); break; case WSMOUSE_TYPE_HIL: printf("HP-HIL\n"); break; case WSMOUSE_TYPE_LUNA: printf("Omron Luna\n"); break; case WSMOUSE_TYPE_DOMAIN: printf("Apollo Domain\n"); break; case WSMOUSE_TYPE_SUN: printf("Sun\n"); break; default: printf("Unknown\n"); break; } } else warnx("unable to open %s", mouse.portname); } /* wsmouse_init : init a wsmouse compatible mouse */ void wsmouse_init(void) { unsigned int res = WSMOUSE_RES_MIN; ioctl(mouse.mfd, WSMOUSEIO_SRES, &res); } /* * Buttons remapping */ /* physical to logical button mapping */ static int p2l[MOUSE_MAXBUTTON] = { MOUSE_BUTTON1, MOUSE_BUTTON2, MOUSE_BUTTON3, MOUSE_BUTTON4, MOUSE_BUTTON5, MOUSE_BUTTON6, MOUSE_BUTTON7, MOUSE_BUTTON8, }; static char * skipspace(char *s) { while (isspace((unsigned char)*s)) ++s; return s; } /* mouse_installmap : install a map between physical and logical buttons */ static int mouse_installmap(char *arg) { int pbutton; int lbutton; char *s; while (*arg) { arg = skipspace(arg); s = arg; while (isdigit((unsigned char)*arg)) ++arg; arg = skipspace(arg); if ((arg <= s) || (*arg != '=')) return FALSE; lbutton = atoi(s); arg = skipspace(++arg); s = arg; while (isdigit((unsigned char)*arg)) ++arg; if (arg <= s || (!isspace((unsigned char)*arg) && *arg != '\0')) return FALSE; pbutton = atoi(s); if (lbutton <= 0 || lbutton > MOUSE_MAXBUTTON) return FALSE; if (pbutton <= 0 || pbutton > MOUSE_MAXBUTTON) return FALSE; p2l[pbutton - 1] = lbutton - 1; } return TRUE; } /* terminate signals handler */ static void terminate(int sig) { struct wscons_event event; unsigned int res; if (mouse.mfd != -1) { event.type = WSCONS_EVENT_WSMOUSED_OFF; ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event); res = WSMOUSE_RES_DEFAULT; ioctl(mouse.mfd, WSMOUSEIO_SRES, &res); close(mouse.mfd); mouse.mfd = -1; } _exit(0); } /* buttons status (for multiple click detection) */ static struct { int count; /* 0: up, 1: single click, 2: double click,... */ struct timeval tv; /* timestamp on the last `up' event */ } buttonstate[MOUSE_MAXBUTTON]; /* * handle button click * Note that an ioctl is sent for each button */ static void mouse_click(struct wscons_event *event) { struct timeval max_date; struct timeval now; struct timeval delay; int i; /* button number */ i = event->value = p2l[event->value]; gettimeofday(&now, NULL); delay.tv_sec = mouse.clickthreshold / 1000; delay.tv_usec = (mouse.clickthreshold % 1000) * 1000; timersub(&now, &delay, &max_date); if (event->type == WSCONS_EVENT_MOUSE_DOWN) { if (timercmp(&max_date, &buttonstate[i].tv, >)) { timerclear(&buttonstate[i].tv); buttonstate[i].count = 1; } else { buttonstate[i].count++; } } else { /* button is up */ buttonstate[i].tv.tv_sec = now.tv_sec; buttonstate[i].tv.tv_usec = now.tv_usec; } /* * we use the time field of wscons_event structure to put the number * of multiple clicks */ if (event->type == WSCONS_EVENT_MOUSE_DOWN) { event->time.tv_sec = buttonstate[i].count; event->time.tv_nsec = 0; } else { /* button is up */ event->time.tv_sec = 0; event->time.tv_nsec = 0; } ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event); } /* workaround for cursor speed on serial mice */ static void normalize_event(struct wscons_event *event) { int dx, dy; int two_power = 1; /* 2: normal speed, 3: slower cursor, 1: faster cursor */ #define NORMALIZE_DIVISOR 3 switch (event->type) { case WSCONS_EVENT_MOUSE_DELTA_X: dx = abs(event->value); while (dx > 2) { two_power++; dx = dx / 2; } event->value = event->value / (NORMALIZE_DIVISOR * two_power); break; case WSCONS_EVENT_MOUSE_DELTA_Y: two_power = 1; dy = abs(event->value); while (dy > 2) { two_power++; dy = dy / 2; } event->value = event->value / (NORMALIZE_DIVISOR * two_power); break; } } /* send a wscons_event to the kernel */ static void treat_event(struct wscons_event *event) { if (IS_MOTION_EVENT(event->type)) { ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, event); } else if (IS_BUTTON_EVENT(event->type) && (uint)event->value < MOUSE_MAXBUTTON) { mouse_click(event); } } /* split a full mouse event into multiples wscons events */ static void split_event(mousestatus_t *act) { struct wscons_event event; int button, i, mask; if (act->dx != 0) { event.type = WSCONS_EVENT_MOUSE_DELTA_X; event.value = act->dx; normalize_event(&event); treat_event(&event); } if (act->dy != 0) { event.type = WSCONS_EVENT_MOUSE_DELTA_Y; event.value = 0 - act->dy; normalize_event(&event); treat_event(&event); } if (act->dz != 0) { event.type = WSCONS_EVENT_MOUSE_DELTA_Z; event.value = act->dz; treat_event(&event); } if (act->dw != 0) { event.type = WSCONS_EVENT_MOUSE_DELTA_W; event.value = act->dw; treat_event(&event); } /* buttons state */ mask = act->flags & MOUSE_BUTTONS; if (mask == 0) /* no button modified */ return; button = MOUSE_BUTTON1DOWN; for (i = 0; (i < MOUSE_MAXBUTTON) && (mask != 0); i++) { if (mask & 1) { event.type = (act->button & button) ? WSCONS_EVENT_MOUSE_DOWN : WSCONS_EVENT_MOUSE_UP; event.value = i; treat_event(&event); } button <<= 1; mask >>= 1; } } /* main function */ static void wsmoused(void) { mousestatus_t action; struct wscons_event event; /* original wscons_event */ struct pollfd pfd[1]; int res; u_char b; /* notify kernel the start of wsmoused */ event.type = WSCONS_EVENT_WSMOUSED_ON; res = ioctl(mouse.cfd, WSDISPLAYIO_WSMOUSED, &event); if (res != 0) { /* the display driver has no getchar() method */ logerr(1, "this display driver has no support for wsmoused(8)"); } bzero(&action, sizeof(action)); bzero(&event, sizeof(event)); bzero(&buttonstate, sizeof(buttonstate)); pfd[0].fd = mouse.mfd; pfd[0].events = POLLIN; /* process mouse data */ for (;;) { if (poll(pfd, 1, INFTIM) <= 0) logwarn("failed to read from mouse"); if (mouse.proto == P_WSCONS) { /* wsmouse supported mouse */ read(mouse.mfd, &event, sizeof(event)); treat_event(&event); } else { /* serial mouse (not supported by wsmouse) */ res = read(mouse.mfd, &b, 1); /* if we have a full mouse event */ if (mouse_protocol(b, &action)) /* split it as multiple wscons_event */ split_event(&action); } } } static void usage(void) { fprintf(stderr, "usage: %s [-2dfi] [-C thresh] [-D device]" " [-M N=M]\n\t[-p device] [-t type]\n", __progname); exit(1); } int main(int argc, char **argv) { unsigned int type; int opt; int i; #define GETOPT_STRING "2dfhip:t:C:D:M:" while ((opt = (getopt(argc, argv, GETOPT_STRING))) != -1) { switch (opt) { case '2': /* on two button mice, right button pastes */ p2l[MOUSE_BUTTON3] = MOUSE_BUTTON2; break; case 'd': ++debug; break; case 'f': nodaemon = TRUE; break; case 'h': usage(); break; case 'i': identify = TRUE; nodaemon = TRUE; break; case 'p': if ((mouse.portname = strdup(optarg)) == NULL) logerr(1, "out of memory"); break; case 't': if (strcmp(optarg, "auto") == 0) { mouse.proto = P_UNKNOWN; mouse.flags &= ~NoPnP; break; } for (i = 0; mouse_names[i] != NULL; i++) if (strcmp(optarg,mouse_names[i]) == 0) { mouse.proto = i; mouse.flags |= NoPnP; break; } if (mouse_names[i] != NULL) break; warnx("no such mouse protocol `%s'", optarg); usage(); break; case 'C': #define MAX_CLICKTHRESHOLD 2000 /* max delay for double click */ mouse.clickthreshold = atoi(optarg); if (mouse.clickthreshold < 0 || mouse.clickthreshold > MAX_CLICKTHRESHOLD) { warnx("invalid threshold `%s': max value is %d", optarg, MAX_CLICKTHRESHOLD); usage(); } break; case 'D': if ((mouse.ttyname = strdup(optarg)) == NULL) logerr(1, "out of memory"); break; case 'M': if (!mouse_installmap(optarg)) { warnx("invalid mapping `%s'", optarg); usage(); } break; default: usage(); } } /* * Use defaults if unspecified */ if (mouse.portname == NULL) mouse.portname = WSMOUSE_DEV; if (mouse.ttyname == NULL) mouse.ttyname = DEFAULT_TTY; if (identify == FALSE) { if ((mouse.cfd = open(mouse.ttyname, O_RDWR, 0)) == -1) logerr(1, "cannot open %s", mouse.ttyname); } if ((mouse.mfd = open(mouse.portname, O_RDONLY | O_NONBLOCK, 0)) == -1) logerr(1, "unable to open %s", mouse.portname); /* * Find out whether the mouse device is a wsmouse device * or a serial device. */ if (ioctl(mouse.mfd, WSMOUSEIO_GTYPE, &type) != -1) mouse.proto = P_WSCONS; else { if (mouse_identify() == P_UNKNOWN) { close(mouse.mfd); logerr(1, "cannot determine mouse type on %s", mouse.portname); } } if (identify == TRUE) { if (mouse.proto == P_WSCONS) wsmouse_identify(); else printf("serial mouse: %s type\n", mouse_name(mouse.proto)); exit(0); } signal(SIGINT, terminate); signal(SIGQUIT, terminate); signal(SIGTERM, terminate); if (mouse.proto == P_WSCONS) wsmouse_init(); else mouse_init(); if (!nodaemon) { openlog(__progname, LOG_PID, LOG_DAEMON); if (daemon(0, 0)) { logerr(1, "failed to become a daemon"); } else { background = TRUE; } } wsmoused(); exit(0); }