Edit

IABSD.fr/xenocara/driver/xf86-input-ws/src/ws.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2023-08-12 16:48:44
    Hash : 4c4398b2
    Message : Avoid close(-1). ok miod@ wsClose() is called twice on X server exit, so this test is needed to avoid calling close a 2nd time with -1.

  • driver/xf86-input-ws/src/ws.c
  • /*
     * Copyright © 2005-2009,2011 Matthieu Herrb
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    /* $OpenBSD: ws.c,v 1.70 2023/08/12 16:48:44 matthieu Exp $ */
    
    #ifdef HAVE_CONFIG_H
    #include "config.h"
    #endif
    
    #include <unistd.h>
    #include <errno.h>
    #include <sys/ioctl.h>
    #include <sys/time.h>
    #include <dev/wscons/wsconsio.h>
    
    #include <xorg-server.h>
    #include <xf86.h>
    #include <xf86_OSproc.h>
    #include <X11/extensions/XI.h>
    #include <X11/extensions/XIproto.h>
    #include <xf86Xinput.h>
    #include <exevents.h>
    #include <mipointer.h>
    #include <extinit.h>
    
    #include "ws.h"
    
    #include <X11/Xatom.h>
    #include "ws-properties.h"
    #include <xserver-properties.h>
    
    static MODULESETUPPROTO(SetupProc);
    static MODULETEARDOWNPROTO(TearDownProc);
    
    static int wsPreInit(InputDriverPtr, InputInfoPtr, int);
    static void wsUnInit(InputDriverPtr, InputInfoPtr, int);
    static int wsProc(DeviceIntPtr, int);
    static int wsDeviceInit(DeviceIntPtr);
    static int wsDeviceOn(DeviceIntPtr);
    static void wsDeviceOff(DeviceIntPtr);
    static void wsReadInput(InputInfoPtr);
    static void wsSendButtons(InputInfoPtr, int);
    static int wsSwitchMode(ClientPtr, DeviceIntPtr, int);
    static Bool wsOpen(InputInfoPtr);
    static void wsClose(InputInfoPtr);
    static void wsControlProc(DeviceIntPtr , PtrCtrl *);
    
    static void wsInitCalibProperty(DeviceIntPtr);
    static int wsSetCalibProperty(DeviceIntPtr, Atom, XIPropertyValuePtr, BOOL);
    
    static Atom prop_calibration;
    static Atom prop_swap;
    
    #ifdef DEBUG
    int ws_debug_level = 0;
    #endif
    
    #define WSMOUSE_MUX_DEVICE "/dev/wsmouse"
    
    static XF86ModuleVersionInfo VersionRec = {
    	"ws",
    	MODULEVENDORSTRING,
    	MODINFOSTRING1,
    	MODINFOSTRING2,
    	XORG_VERSION_CURRENT,
    	PACKAGE_VERSION_MAJOR,
    	PACKAGE_VERSION_MINOR,
    	PACKAGE_VERSION_PATCHLEVEL,
    	ABI_CLASS_XINPUT,
    	ABI_XINPUT_VERSION,
    	MOD_CLASS_XINPUT,
    	{0, 0, 0, 0}
    };
    
    _X_EXPORT XF86ModuleData wsModuleData = {
    	.vers     = &VersionRec,
    	.setup    = SetupProc,
    	.teardown = TearDownProc
    };
    
    _X_EXPORT InputDriverRec WS = {
    	.driverVersion   = 1,
    	.driverName      = "ws",
    	.Identify        = NULL,
    	.PreInit         = wsPreInit,
    	.UnInit          = wsUnInit,
    	.module          = NULL,
    	.default_options = NULL,
    	.capabilities    = 0
    };
    
    static pointer
    SetupProc(pointer module, pointer options, int *errmaj, int *errmin)
    {
    	static Bool Initialised = FALSE;
    
    	if (!Initialised) {
    		xf86AddInputDriver(&WS, module, 0);
    		Initialised = TRUE;
    	}
    	return module;
    }
    
    static void
    TearDownProc(pointer p)
    {
    	DBG(1, ErrorF("WS TearDownProc called\n"));
    }
    
    static int
    wsPreInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
    {
    	WSDevicePtr priv;
    	MessageType buttons_from = X_CONFIG;
    	char *s;
    	int i, phy_btn = 1, rc = BadValue;
    
    	priv = (WSDevicePtr)calloc(1, sizeof(WSDeviceRec));
    	if (priv == NULL) {
    		rc = BadAlloc;
    		goto fail;
    	}
    	pInfo->private = priv;
    
    #ifdef DEBUG
    	ws_debug_level = xf86SetIntOption(pInfo->options, "DebugLevel",
    	    ws_debug_level);
    	xf86IDrvMsg(pInfo, X_INFO, "debuglevel %d\n", ws_debug_level);
    #endif
    	priv->devName = xf86SetStrOption(pInfo->options, "Device", NULL);
    	if (priv->devName == NULL) {
    		xf86IDrvMsg(pInfo, X_ERROR, "No Device specified.\n");
    		goto fail;
    	}
    	priv->buttons = xf86SetIntOption(pInfo->options, "Buttons", 0);
    	if (priv->buttons == 0) {
    		priv->buttons = DFLTBUTTONS;
    		buttons_from = X_DEFAULT;
    	}
    
    	/* Check for user-defined button mapping */
    	s = xf86SetStrOption(pInfo->options, "ButtonMapping", NULL);
    	if (s) {
    		char *map = s, *end;
    		int btn;
    
    		do {
    			btn = strtol(map, &end, 10);
    
    			if (end == map || btn < 0 || btn > NBUTTONS) {
    				xf86IDrvMsg(pInfo, X_ERROR,
    				    "Invalid button mapping. Using defaults\n");
    				phy_btn = 1; /* ensure defaults start at 1 */
    				break;
    			}
    
    			priv->btnmap[phy_btn++] = btn;
    			map = end;
    		} while (end && *end != '\0' && phy_btn <= NBUTTONS);
    		free(s);
    	}
    
    	for (i = phy_btn; i <= NBUTTONS; i++)
    		priv->btnmap[i] = i;
    
    	wsWheelHandleButtonMap(pInfo, &(priv->Z), "ZAxisMapping", "4 5");
    	wsWheelHandleButtonMap(pInfo, &(priv->W), "WAxisMapping", "6 7");
    
    	priv->screen_no = xf86SetIntOption(pInfo->options, "ScreenNo", 0);
    	xf86IDrvMsg(pInfo, X_CONFIG, "associated screen: %d\n",
    	    priv->screen_no);
    	if (priv->screen_no < 0 || priv->screen_no >= screenInfo.numScreens)
    		priv->screen_no = 0;
    
    	priv->swap_axes = xf86SetBoolOption(pInfo->options, "SwapXY", 0);
    	if (priv->swap_axes) {
    		xf86IDrvMsg(pInfo, X_CONFIG,
    		    "device will work with X and Y axes swapped\n");
    	}
    	priv->inv_x = 0;
    	priv->inv_y = 0;
    	s = xf86SetStrOption(pInfo->options, "Rotate", NULL);
    	if (s) {
    		if (xf86NameCmp(s, "CW") == 0) {
    			priv->inv_x = 1;
    			priv->inv_y = 0;
    			priv->swap_axes = 1;
    		} else if (xf86NameCmp(s, "CCW") == 0) {
    			priv->inv_x = 0;
    			priv->inv_y = 1;
    			priv->swap_axes = 1;
    		} else if (xf86NameCmp(s, "UD") == 0) {
    			priv->inv_x = 1;
    			priv->inv_y = 1;
    		} else {
    			xf86IDrvMsg(pInfo, X_ERROR, "\"%s\" is not a valid "
    			    "value for Option \"Rotate\"\n", s);
    			xf86IDrvMsg(pInfo, X_ERROR, "Valid options are "
    			    "\"CW\", \"CCW\", or \"UD\"\n");
    		}
    		free(s);
    	}
    	if (wsOpen(pInfo) != Success)
    		goto fail;
    	if (ioctl(pInfo->fd, WSMOUSEIO_GTYPE, &priv->type) != 0) {
    		if (strcmp(priv->devName, WSMOUSE_MUX_DEVICE) == 0)
    			/*
    			 * No mice are currently connected to the mux, assume
    			 * any traffic we see on it later will come from a USB
    			 * mouse.
    			 */
    			priv->type = WSMOUSE_TYPE_USB;
    		else
    			goto fail;
    	}
    	if (priv->type == WSMOUSE_TYPE_TPANEL) {
    		pInfo->type_name = XI_TOUCHSCREEN;
    		priv->raw = xf86SetBoolOption(pInfo->options, "Raw", 1);
    		if (priv->raw) {
    			xf86IDrvMsg(pInfo, X_CONFIG,
    			    "device will work in raw mode\n");
    		}
    	} else {
    		pInfo->type_name = XI_MOUSE;
    		priv->raw = 0;
    	}
    
    	if (priv->type == WSMOUSE_TYPE_TPANEL && priv->raw) {
    		if (ioctl(pInfo->fd, WSMOUSEIO_GCALIBCOORDS,
    		    &priv->coords) != 0) {
    			xf86IDrvMsg(pInfo, X_ERROR,
    			    "GCALIBCOORDS failed %s\n", strerror(errno));
    			goto fail;
    		}
    
    		/* get default coordinate space from kernel */
    		priv->min_x = priv->coords.minx;
    		priv->max_x = priv->coords.maxx;
    		priv->min_y = priv->coords.miny;
    		priv->max_y = priv->coords.maxy;
    	} else {
    		/* in calibrated mode, coordinate space, is screen coords */
    		priv->min_x = 0;
    		priv->max_x = screenInfo.screens[priv->screen_no]->width - 1;
    		priv->min_y = 0;
    		priv->max_y = screenInfo.screens[priv->screen_no]->height - 1;
    	}
    	/* Allow options to override this */
    	priv->min_x = xf86SetIntOption(pInfo->options, "MinX", priv->min_x);
    	xf86IDrvMsg(pInfo, X_INFO, "minimum x position: %d\n", priv->min_x);
    	priv->max_x = xf86SetIntOption(pInfo->options, "MaxX", priv->max_x);
    	xf86IDrvMsg(pInfo, X_INFO, "maximum x position: %d\n", priv->max_x);
    	priv->min_y = xf86SetIntOption(pInfo->options, "MinY", priv->min_y);
    	xf86IDrvMsg(pInfo, X_INFO, "minimum y position: %d\n", priv->min_y);
    	priv->max_y = xf86SetIntOption(pInfo->options, "MaxY", priv->max_y);
    	xf86IDrvMsg(pInfo, X_INFO, "maximum y position: %d\n", priv->max_y);
    
    	pInfo->device_control = wsProc;
    	pInfo->read_input = wsReadInput;
    	pInfo->switch_mode = wsSwitchMode;
    
    	xf86IDrvMsg(pInfo, buttons_from, "Buttons: %d\n", priv->buttons);
    
    	wsClose(pInfo);
    
    	wsmbEmuPreInit(pInfo);
    	wsWheelEmuPreInit(pInfo);
    	return Success;
    
    fail:
    	if (pInfo->fd >= 0)
    		wsClose(pInfo);
    	if (priv != NULL) {
    		free(priv);
    		pInfo->private = NULL;
    	}
    	return rc;
    }
    
    static void
    wsUnInit(InputDriverPtr drv, InputInfoPtr pInfo, int flags)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    
    	if (priv) {
    		free(priv->devName);
    		priv->devName = NULL;
    	}
    	xf86DeleteInput(pInfo, flags);
    }
    
    static int
    wsProc(DeviceIntPtr pWS, int what)
    {
    	InputInfoPtr pInfo = (InputInfoPtr)pWS->public.devicePrivate;
    
    	switch (what) {
    	case DEVICE_INIT:
    		return wsDeviceInit(pWS);
    
    	case DEVICE_ON:
    		return wsDeviceOn(pWS);
    
    	case DEVICE_OFF:
    		wsDeviceOff(pWS);
    		break;
    
    	case DEVICE_CLOSE:
    		DBG(1, ErrorF("WS DEVICE_CLOSE\n"));
    		wsClose(pInfo);
    		break;
    
    	default:
    		xf86IDrvMsg(pInfo, X_ERROR, "unknown command %d\n", what);
    		return !Success;
    	}
    	return Success;
    }
    
    static int
    wsDeviceInit(DeviceIntPtr pWS)
    {
    	InputInfoPtr pInfo = (InputInfoPtr)pWS->public.devicePrivate;
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	int xmin, xmax, ymin, ymax;
    	Atom btn_labels[NBUTTONS] = {0};
    	Atom axes_labels[NAXES] = {0};
    
    	DBG(1, ErrorF("WS DEVICE_INIT\n"));
    
    	btn_labels[0] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_LEFT);
    	btn_labels[1] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_MIDDLE);
    	btn_labels[2] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_RIGHT);
    	if (!InitButtonClassDeviceStruct(pWS, min(priv->buttons, NBUTTONS),
    	    btn_labels, priv->btnmap))
    		return !Success;
    
    	if (priv->type == WSMOUSE_TYPE_TPANEL) {
    		xmin = priv->min_x;
    		xmax = priv->max_x;
    		ymin = priv->min_y;
    		ymax = priv->max_y;
    	} else {
    		xmin = -1;
    		xmax = -1;
    		ymin = -1;
    		ymax = -1;
    	}
    
    	if (priv->swap_axes) {
    		int tmp;
    		tmp = xmin;
    		xmin = ymin;
    		ymin = tmp;
    		tmp = xmax;
    		xmax = ymax;
    		ymax = tmp;
    	}
    	if (priv->type == WSMOUSE_TYPE_TPANEL) {
    		axes_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_X);
    		axes_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_Y);
    	} else {
    		axes_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_X);
    		axes_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_Y);
    	}
    	axes_labels[HSCROLL_AXIS] =
    	    XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);
    	axes_labels[VSCROLL_AXIS] =
    	    XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
    	if (!InitValuatorClassDeviceStruct(pWS,
    	    NAXES, axes_labels, GetMotionHistorySize(),
    	    priv->type == WSMOUSE_TYPE_TPANEL ? Absolute : Relative))
    		return !Success;
    	if (!InitPtrFeedbackClassDeviceStruct(pWS, wsControlProc))
    		return !Success;
    
    	xf86InitValuatorAxisStruct(pWS, 0,
    	    axes_labels[0],
    	    xmin, xmax, 1, 0, 1,
    	    priv->type == WSMOUSE_TYPE_TPANEL ? Absolute : Relative);
    	xf86InitValuatorDefaults(pWS, 0);
    
    	xf86InitValuatorAxisStruct(pWS, 1,
    	    axes_labels[1],
    	    ymin, ymax, 1, 0, 1,
    	    priv->type == WSMOUSE_TYPE_TPANEL ? Absolute : Relative);
    	xf86InitValuatorDefaults(pWS, 1);
    
    	xf86InitValuatorAxisStruct(pWS, HSCROLL_AXIS,
    	    axes_labels[HSCROLL_AXIS], 0, -1, 0, 0, 0, Relative);
    	xf86InitValuatorAxisStruct(pWS, VSCROLL_AXIS,
    	    axes_labels[VSCROLL_AXIS], 0, -1, 0, 0, 0, Relative);
    	priv->scroll_mask = valuator_mask_new(MAX_VALUATORS);
    	if (!priv->scroll_mask)
    		return !Success;
    
    	/*
    	 * The value of an HSCROLL or VSCROLL event is the fraction
    	 *         motion_delta / scroll_distance
    	 * in [*.12] fixed-point format.  The 'increment' attribute of the
    	 * scroll axes is constant:
    	 */
    	SetScrollValuator(pWS, HSCROLL_AXIS, SCROLL_TYPE_HORIZONTAL, 4096, 0);
    	SetScrollValuator(pWS, VSCROLL_AXIS, SCROLL_TYPE_VERTICAL, 4096, 0);
    
    	pWS->public.on = FALSE;
    	if (wsOpen(pInfo) != Success) {
    		return !Success;
    	}
    
    	if (priv->type == WSMOUSE_TYPE_TPANEL)
    		wsInitCalibProperty(pWS);
    	wsmbEmuInitProperty(pWS);
    	wsWheelEmuInitProperty(pWS);
    	return Success;
    }
    
    static int
    wsDeviceOn(DeviceIntPtr pWS)
    {
    	InputInfoPtr pInfo = (InputInfoPtr)pWS->public.devicePrivate;
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	struct wsmouse_calibcoords coords;
    
    	DBG(1, ErrorF("WS DEVICE ON\n"));
    	if ((pInfo->fd < 0) && (wsOpen(pInfo) != Success)) {
    		xf86IDrvMsg(pInfo, X_ERROR, "wsOpen failed %s\n",
    		    strerror(errno));
    		return !Success;
    	}
    
    	if (priv->type == WSMOUSE_TYPE_TPANEL) {
    		/* get calibration values */
    		if (ioctl(pInfo->fd, WSMOUSEIO_GCALIBCOORDS, &coords) != 0) {
    			xf86IDrvMsg(pInfo, X_ERROR, "GCALIBCOORDS failed %s\n",
    			    strerror(errno));
    			return !Success;
    		}
    		memcpy(&priv->coords, &coords, sizeof coords);
    		/* set raw mode */
    		if (coords.samplelen != priv->raw) {
    			coords.samplelen = priv->raw;
    			if (ioctl(pInfo->fd, WSMOUSEIO_SCALIBCOORDS,
    			    &coords) != 0) {
    				xf86IDrvMsg(pInfo, X_ERROR,
    				    "SCALIBCOORDS failed %s\n",
    				    strerror(errno));
    				return !Success;
    			}
    		}
    	}
    	if (priv->remove_timer == NULL) {
    		/* allocate here where it's safe */
    		priv->remove_timer = TimerSet(priv->remove_timer,
    					      0, 0, NULL, NULL);
    	}
    	xf86AddEnabledDevice(pInfo);
    	wsmbEmuOn(pInfo);
    	pWS->public.on = TRUE;
    	return Success;
    }
    
    static void
    wsDeviceOff(DeviceIntPtr pWS)
    {
    	InputInfoPtr pInfo = (InputInfoPtr)pWS->public.devicePrivate;
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	struct wsmouse_calibcoords coords;
    
    	DBG(1, ErrorF("WS DEVICE OFF\n"));
    	wsmbEmuFinalize(pInfo);
    	if (priv->type == WSMOUSE_TYPE_TPANEL) {
    		/* Restore calibration data */
    		memcpy(&coords, &priv->coords, sizeof coords);
    		if (ioctl(pInfo->fd, WSMOUSEIO_SCALIBCOORDS, &coords) != 0) {
    			xf86IDrvMsg(pInfo, X_ERROR, "SCALIBCOORDS failed %s\n",
    			    strerror(errno));
    		}
    	}
    	if (pInfo->fd >= 0) {
    		xf86RemoveEnabledDevice(pInfo);
    		wsClose(pInfo);
    	}
    	pWS->public.on = FALSE;
    }
    
    /*
     * Called as an OsTimer() callback to remove the device while xf86Wakeup()
     * isn't playing with the list.
     */
    static CARD32
    wsRemoveMouse(OsTimerPtr timer, CARD32 now, pointer arg)
    {
    	InputInfoPtr pInfo = (InputInfoPtr) arg;
    
    	xf86DisableDevice(pInfo->dev, TRUE);
    
    	return 0;	/* don't set to run again */
    }
    
    static size_t
    wsReadEvents(InputInfoPtr pInfo)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	ssize_t len;
    
    	priv->events_count = priv->events_pos = 0;
    	len = read(pInfo->fd, priv->events, sizeof(priv->events));
    	if (len < 0) {
    		if (errno == EIO) {
    			xf86IDrvMsg(pInfo, X_ERROR,
    			    "device no longer present - removing: %s\n",
    			    strerror(errno));
    			xf86RemoveEnabledDevice(pInfo);
    			priv->remove_timer = TimerSet(priv->remove_timer, 0, 1,
    						      wsRemoveMouse, pInfo);
    		} else if (errno != EAGAIN)
    			xf86IDrvMsg(pInfo, X_ERROR, "read error %s\n",
    			    strerror(errno));
    	} else if (len % sizeof(struct wscons_event)) {
    		xf86IDrvMsg(pInfo, X_ERROR,
    		    "read error, invalid number of bytes\n");
    	} else {
    		priv->events_count = len / sizeof(struct wscons_event);
    	}
    
    	return priv->events_count;
    }
    
    static struct wscons_event *
    wsGetEvent(InputInfoPtr pInfo)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	struct wscons_event *event;
    
    	if (priv->events_count == 0 && wsReadEvents(pInfo) == 0)
    		return NULL;
    
    	event = &priv->events[priv->events_pos];
    	priv->events_pos++;
    	priv->events_count--;
    
    	return event;
    }
    
    static Bool
    wsReadHwState(InputInfoPtr pInfo, wsHwState *hw)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	struct wscons_event *event;
    
    	bzero(hw, sizeof(wsHwState));
    	hw->buttons = priv->lastButtons;
    	if (!priv->swap_axes) {
    		hw->ax = priv->old_ax;
    		hw->ay = priv->old_ay;
    	} else {
    		hw->ax = priv->old_ay;
    		hw->ay = priv->old_ax;
    	}
    
    	while ((event = wsGetEvent(pInfo)) != NULL) {
    		switch (event->type) {
    		case WSCONS_EVENT_MOUSE_UP:
    			hw->buttons &= ~(1 << event->value);
    			DBG(4, ErrorF("Button %d up %x\n", event->value,
    			    hw->buttons));
    			break;
    		case WSCONS_EVENT_MOUSE_DOWN:
    			hw->buttons |= (1 << event->value);
    			DBG(4, ErrorF("Button %d down %x\n", event->value,
    			    hw->buttons));
    			break;
    		case WSCONS_EVENT_MOUSE_DELTA_X:
    			hw->dx = event->value;
    			DBG(4, ErrorF("Relative X %d\n", event->value));
    			break;
    		case WSCONS_EVENT_MOUSE_DELTA_Y:
    			hw->dy = -event->value;
    			DBG(4, ErrorF("Relative Y %d\n", event->value));
    			break;
    		case WSCONS_EVENT_MOUSE_DELTA_Z:
    			hw->dz = event->value;
    			DBG(4, ErrorF("Relative Z %d\n", event->value));
    			break;
    		case WSCONS_EVENT_MOUSE_DELTA_W:
    			hw->dw = event->value;
    			DBG(4, ErrorF("Relative W %d\n", event->value));
    			break;
    		case WSCONS_EVENT_MOUSE_ABSOLUTE_X:
    			hw->ax = event->value;
    			if (priv->inv_x)
    				hw->ax = priv->max_x - hw->ax + priv->min_x;
    			DBG(4, ErrorF("Absolute X %d\n", event->value));
    			break;
    		case WSCONS_EVENT_MOUSE_ABSOLUTE_Y:
    			hw->ay = event->value;
    			if (priv->inv_y)
    				hw->ay = priv->max_y - hw->ay + priv->min_y;
    			DBG(4, ErrorF("Absolute Y %d\n", event->value));
    			break;
    		case WSCONS_EVENT_MOUSE_ABSOLUTE_Z:
    		case WSCONS_EVENT_MOUSE_ABSOLUTE_W:
    			/* ignore those */
    			continue;
    		case WSCONS_EVENT_SYNC:
    			DBG(4, ErrorF("Sync\n"));
    			return TRUE;
    		case WSCONS_EVENT_HSCROLL:
    			hw->hscroll = event->value;
    			DBG(4, ErrorF("Horiz. Scrolling %d\n", event->value));
    			return TRUE;
    		case WSCONS_EVENT_VSCROLL:
    			hw->vscroll = event->value;
    			DBG(4, ErrorF("Vert. Scrolling %d\n", event->value));
    			return TRUE;
    		default:
    			xf86IDrvMsg(pInfo, X_WARNING,
    			    "bad wsmouse event type=%d\n", event->type);
    			continue;
    		}
    #ifdef __NetBSD__
    		/*
    		 * XXX NetBSD reads only one event.
    		 * WSCONS_EVENT_SYNC is not supported yet.
    		 */
    		return TRUE;
    #endif
    	}
    
    	return FALSE;
    }
    
    static void
    wsReadInput(InputInfoPtr pInfo)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	wsHwState hw;
    
    	if (!wsReadHwState(pInfo, &hw))
    		return;
    
    	if ((hw.dx || hw.dy) && !wsWheelEmuFilterMotion(pInfo, hw.dx, hw.dy)) {
    		/* relative motion event */
    		DBG(3, ErrorF("postMotionEvent dX %d dY %d\n", hw.dx, hw.dy));
    		xf86PostMotionEvent(pInfo->dev, 0, 0, 2, hw.dx, hw.dy);
    	}
    	if (hw.dz && priv->Z.negative != WS_NOMAP
    	    && priv->Z.positive != WS_NOMAP) {
    		int zbutton;
    		zbutton = (hw.dz < 0) ? priv->Z.negative : priv->Z.positive;
    		DBG(4, ErrorF("Z -> button %d (%d)\n", zbutton, abs(hw.dz)));
    		wsButtonClicks(pInfo, zbutton, abs(hw.dz));
    	}
    	if (hw.dw && priv->W.negative != WS_NOMAP
    	    && priv->W.positive != WS_NOMAP) {
    		int wbutton;
    		wbutton = (hw.dw < 0) ? priv->W.negative : priv->W.positive;
    		DBG(4, ErrorF("W -> button %d (%d)\n", wbutton, abs(hw.dw)));
    		wsButtonClicks(pInfo, wbutton, abs(hw.dw));
    	}
    	if (hw.hscroll || hw.vscroll) {
    		valuator_mask_zero(priv->scroll_mask);
    		valuator_mask_set_double(priv->scroll_mask,
    		    HSCROLL_AXIS, (double) hw.hscroll);
    		valuator_mask_set_double(priv->scroll_mask,
    		    VSCROLL_AXIS, (double) hw.vscroll);
    		xf86PostMotionEventM(pInfo->dev, FALSE, priv->scroll_mask);
    	}
    	if (priv->lastButtons != hw.buttons) {
    		/* button event */
    		wsSendButtons(pInfo, hw.buttons);
    	}
    	if (priv->swap_axes) {
    		int tmp;
    
    		tmp = hw.ax;
    		hw.ax = hw.ay;
    		hw.ay = tmp;
    	}
    	if ((hw.ax != priv->old_ax) || (hw.ay != priv->old_ay)) {
    		int xdelta = hw.ax - priv->old_ax;
    		int ydelta = hw.ay - priv->old_ay;
    		priv->old_ax = hw.ax;
    		priv->old_ay = hw.ay;
    		if (wsWheelEmuFilterMotion(pInfo, xdelta, ydelta))
    			return;
    
    		/* absolute position event */
    		DBG(3, ErrorF("postMotionEvent X %d Y %d\n", hw.ax, hw.ay));
    		xf86PostMotionEvent(pInfo->dev, 1, 0, 2, hw.ax, hw.ay);
    	}
    }
    
    static void
    wsSendButtons(InputInfoPtr pInfo, int buttons)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	int change, button, press;
    
    	change = buttons ^ priv->lastButtons;
    	while (change) {
    		button = ffs(change);
    		press = buttons & (1 << (button - 1));
    		change &= ~(1 << (button - 1));
    
    		if (wsWheelEmuFilterButton(pInfo, button, press))
    			continue;
    
    		if (wsmbEmuFilterEvent(pInfo, button, press))
    			continue;
    
    		xf86PostButtonEvent(pInfo->dev, TRUE, button, press, 0, 0);
    		DBG(3, ErrorF("post button event %d %d\n", button, press));
    	}
    	priv->lastButtons = buttons;
    }
    
    static int
    wsSwitchMode(ClientPtr client, DeviceIntPtr dev, int mode)
    {
    	return BadMatch;
    }
    
    static Bool
    wsOpen(InputInfoPtr pInfo)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    #ifdef __NetBSD__
    	int version = WSMOUSE_EVENT_VERSION;
    #endif
    
    	DBG(1, ErrorF("WS open %s\n", priv->devName));
    	pInfo->fd = xf86OpenSerial(pInfo->options);
    	if (pInfo->fd == -1) {
    		xf86IDrvMsg(pInfo, X_ERROR, "cannot open input device\n");
    		return !Success;
    	}
    #ifdef __NetBSD__
    	if (ioctl(pInfo->fd, WSMOUSEIO_SETVERSION, &version) == -1) {
    		xf86IDrvMsg(pInfo, X_ERROR,
    		    "cannot set wsmouse event version\n");
    		return !Success;
    	}
    #endif
    	return Success;
    }
    
    static void
    wsClose(InputInfoPtr pInfo)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    
    	if (priv->remove_timer != NULL) {
    		TimerFree(priv->remove_timer);
    		priv->remove_timer = NULL;
    	}
    	if (pInfo->fd >= 0)
    		xf86CloseSerial(pInfo->fd);
    	pInfo->fd = -1;
    }
    
    static void
    wsControlProc(DeviceIntPtr device, PtrCtrl *ctrl)
    {
    	/* Nothing to do, dix handles all settings */
    	DBG(1, ErrorF("wsControlProc\n"));
    }
    
    static void
    wsInitCalibProperty(DeviceIntPtr device)
    {
    	InputInfoPtr pInfo = (InputInfoPtr)device->public.devicePrivate;
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	int rc;
    
    	DBG(1, ErrorF("wsInitCalibProperty\n"));
    
    	prop_calibration = MakeAtom(WS_PROP_CALIBRATION,
    	    strlen(WS_PROP_CALIBRATION), TRUE);
    	rc = XIChangeDeviceProperty(device, prop_calibration, XA_INTEGER, 32,
    	    PropModeReplace, 4, &priv->min_x, FALSE);
    	if (rc != Success) {
    		xf86IDrvMsg(pInfo, X_ERROR,
    		    "cannot create device property %s: %d\n",
    		    WS_PROP_CALIBRATION, rc);
    		return;
    	}
    	XISetDevicePropertyDeletable(device, prop_calibration, FALSE);
    
    	prop_swap = MakeAtom(WS_PROP_SWAP_AXES,
    	    strlen(WS_PROP_SWAP_AXES), TRUE);
    	rc = XIChangeDeviceProperty(device, prop_swap, XA_INTEGER, 8,
    	    PropModeReplace, 1, &priv->swap_axes, FALSE);
    	if (rc != Success) {
    		xf86IDrvMsg(pInfo, X_ERROR,
    		    "cannot create device property %s: %d\n",
    		    WS_PROP_SWAP_AXES, rc);
    		return;
    	}
    	XISetDevicePropertyDeletable(device, prop_swap, FALSE);
    
    	XIRegisterPropertyHandler(device, wsSetCalibProperty, NULL, NULL);
    
    	return;
    }
    
    static int
    wsSetCalibProperty(DeviceIntPtr device, Atom atom, XIPropertyValuePtr val,
        BOOL checkonly)
    {
    	InputInfoPtr pInfo = (InputInfoPtr)device->public.devicePrivate;
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	struct wsmouse_calibcoords coords;
    	int need_update = 0;
    	AxisInfoPtr ax = device->valuator->axes,
    		    ay = device->valuator->axes + 1;
    
    	DBG(1, ErrorF("wsSetCalibProperty %s\n", NameForAtom(atom)));
    
    	if (atom == prop_calibration) {
    		if (val->format != 32 || val->type != XA_INTEGER)
    			return BadMatch;
    		if (val->size != 4 && val->size != 0)
    			return BadMatch;
    		if (!checkonly) {
    			if (val->size == 0) {
    				DBG(1, ErrorF(" uncalibrate\n"));
    				priv->min_x = 0;
    				priv->max_x = -1;
    				priv->min_y = 0;
    				priv->max_y = -1;
    			} else {
    				priv->min_x = ((int *)(val->data))[0];
    				priv->max_x = ((int *)(val->data))[1];
    				priv->min_y = ((int *)(val->data))[2];
    				priv->max_y = ((int *)(val->data))[3];
    				DBG(1, ErrorF(" calibrate %d %d %d %d\n",
    					priv->min_x, priv->max_x,
    					priv->min_y, priv->max_y));
    				need_update++;
    			}
    			/* Update axes descriptors */
    			if (!priv->swap_axes) {
    				ax->min_value = priv->min_x;
    				ax->max_value = priv->max_x;
    				ay->min_value = priv->min_y;
    				ay->max_value = priv->max_y;
    			} else {
    				ax->min_value = priv->min_y;
    				ax->max_value = priv->max_y;
    				ay->min_value = priv->min_x;
    				ay->max_value = priv->max_x;
    			}
    		}
    	} else if (atom == prop_swap) {
    		if (val->format != 8 || val->type != XA_INTEGER ||
    		    val->size != 1)
    			return BadMatch;
    		if (!checkonly) {
    			priv->swap_axes = *((BOOL *)val->data);
    			DBG(1, ErrorF("swap_axes %d\n", priv->swap_axes));
    			need_update++;
    		}
    	}
    	if (need_update) {
    		/* Update the saved values to be restored on device off */
    		priv->coords.minx = priv->min_x;
    		priv->coords.maxx = priv->max_x;
    		priv->coords.miny = priv->min_y;
    		priv->coords.maxy = priv->max_y;
    		priv->coords.swapxy = priv->swap_axes;
    
    		/* Update the kernel calibration table */
    		coords.minx = priv->min_x;
    		coords.maxx = priv->max_x;
    		coords.miny = priv->min_y;
    		coords.maxy = priv->max_y;
    		coords.swapxy = priv->swap_axes;
    		coords.samplelen = priv->raw;
    		coords.resx = priv->coords.resx;
    		coords.resy = priv->coords.resy;
    		if (ioctl(pInfo->fd, WSMOUSEIO_SCALIBCOORDS, &coords) != 0) {
    			xf86IDrvMsg(pInfo, X_ERROR, "SCALIBCOORDS failed %s\n",
    			    strerror(errno));
    		}
    	}
    	return Success;
    }
    
    void
    wsWheelHandleButtonMap(InputInfoPtr pInfo, WheelAxisPtr pAxis,
        const char* axis_name, const char* default_value)
    {
    	WSDevicePtr priv = (WSDevicePtr)pInfo->private;
    	char *option_string;
    	int b1, b2;
    
    	pAxis->negative = pAxis->positive = WS_NOMAP;
    	pAxis->traveled_distance = 0;
    
    	option_string = xf86SetStrOption(pInfo->options, axis_name,
    	    default_value);
    	if (option_string) {
    		if (sscanf(option_string, "%d %d", &b1, &b2) == 2 &&
    		    b1 > 0 && b1 <= NBUTTONS &&
    		    b2 > 0 && b2 <= NBUTTONS) {
    			xf86IDrvMsg(pInfo, X_CONFIG, "%s: buttons %d and %d\n",
    			    axis_name, b1, b2);
    
    			pAxis->negative = b1;
    			pAxis->positive = b2;
    
    			if (max(b1, b2) > priv->buttons)
    				priv->buttons = max(b1, b2);
    		} else {
    			xf86IDrvMsg(pInfo, X_WARNING,
    			    "Invalid %s value: \"%s\"\n",
    			    axis_name, option_string);
    		}
    		free(option_string);
    	}
    }
    
    void
    wsButtonClicks(InputInfoPtr pInfo, int button, int count)
    {
    	int i;
    
    	for (i = 0; i < count; i++) {
    		xf86PostButtonEvent(pInfo->dev, TRUE, button, 1, 0, 0);
    		xf86PostButtonEvent(pInfo->dev, TRUE, button, 0, 0, 0);
    	}
    }