diff options
author | Matthieu Herrb <matthieu@openbsd.org> | 2009-11-26 16:42:06 +0000 |
---|---|---|
committer | Matthieu Herrb <matthieu@openbsd.org> | 2009-11-26 16:42:06 +0000 |
commit | 3d6556126459608d238f7cfa16793a0dc5eb2827 (patch) | |
tree | f9ccd55a7f31ce1dc77496511d7e2b08af07d955 | |
parent | 81f11e013a64fe8578c15f3bdb35fdf3c5d8e66e (diff) |
Add middle mouse button emulation to xf86-input-ws.
Code stolen from evdev, that stole it from xf86-input mouse.
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/emumb.c | 427 | ||||
-rw-r--r-- | src/ws.c | 74 |
3 files changed, 457 insertions, 49 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1ed52fa..989f245 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,4 +21,7 @@ AM_CFLAGS = $(XORG_CFLAGS) INCLUDES=-I$(top_srcdir)/include/ -@DRIVER_NAME@_drv_la_SOURCES = @DRIVER_NAME@.c +@DRIVER_NAME@_drv_la_SOURCES = \ + @DRIVER_NAME@.c \ + @DRIVER_NAME@.h \ + emumb.c diff --git a/src/emumb.c b/src/emumb.c new file mode 100644 index 0000000..c370e2f --- /dev/null +++ b/src/emumb.c @@ -0,0 +1,427 @@ +/* $OpenBSD$ */ +/* + * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany. + * Copyright 1993 by David Dawes <dawes@xfree86.org> + * Copyright 2002 by SuSE Linux AG, Author: Egbert Eich + * Copyright 1994-2002 by The XFree86 Project, Inc. + * Copyright 2002 by Paul Elliott + * (Ported from xf86-input-mouse, above copyrights taken from there) + * Copyright © 2008 University of South Australia + * Copyright © 2009 Matthieu Herrb <matthieu@herrb.eu> + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of the authors + * not be used in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. The authors make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, 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. + * + */ + +/* Middle mouse button emulation code. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <X11/Xatom.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 <ws-properties.h> +#include "ws.h" + +enum { + MBEMU_DISABLED = 0, + MBEMU_ENABLED, + MBEMU_AUTO +}; + +#ifdef HAVE_PROPERTIES +static Atom prop_mbemu = 0; /* Middle button emulation on/off property */ +static Atom prop_mbtimeout = 0; /* Middle button timeout property */ +#endif +/* + * Lets create a simple finite-state machine for 3 button emulation: + * + * We track buttons 1 and 3 (left and right). There are 11 states: + * 0 ground - initial state + * 1 delayed left - left pressed, waiting for right + * 2 delayed right - right pressed, waiting for left + * 3 pressed middle - right and left pressed, emulated middle sent + * 4 pressed left - left pressed and sent + * 5 pressed right - right pressed and sent + * 6 released left - left released after emulated middle + * 7 released right - right released after emulated middle + * 8 repressed left - left pressed after released left + * 9 repressed right - right pressed after released right + * 10 pressed both - both pressed, not emulating middle + * + * At each state, we need handlers for the following events + * 0: no buttons down + * 1: left button down + * 2: right button down + * 3: both buttons down + * 4: emulate3Timeout passed without a button change + * Note that button events are not deltas, they are the set of buttons being + * pressed now. It's possible (ie, mouse hardware does it) to go from (eg) + * left down to right down without anything in between, so all cases must be + * handled. + * + * a handler consists of three values: + * 0: action1 + * 1: action2 + * 2: new emulation state + * + * action > 0: ButtonPress + * action = 0: nothing + * action < 0: ButtonRelease + * + * The comment preceeding each section is the current emulation state. + * The comments to the right are of the form + * <button state> (<events>) -> <new emulation state> + * which should be read as + * If the buttons are in <button state>, generate <events> then go to + * <new emulation state>. + */ +static signed char stateTab[11][5][3] = { +/* 0 ground */ + { + { 0, 0, 0 }, /* nothing -> ground (no change) */ + { 0, 0, 1 }, /* left -> delayed left */ + { 0, 0, 2 }, /* right -> delayed right */ + { 2, 0, 3 }, /* left & right (middle press) -> pressed middle */ + { 0, 0, -1 } /* timeout N/A */ + }, +/* 1 delayed left */ + { + { 1, -1, 0 }, /* nothing (left event) -> ground */ + { 0, 0, 1 }, /* left -> delayed left (no change) */ + { 1, -1, 2 }, /* right (left event) -> delayed right */ + { 2, 0, 3 }, /* left & right (middle press) -> pressed middle */ + { 1, 0, 4 }, /* timeout (left press) -> pressed left */ + }, +/* 2 delayed right */ + { + { 3, -3, 0 }, /* nothing (right event) -> ground */ + { 3, -3, 1 }, /* left (right event) -> delayed left (no change) */ + { 0, 0, 2 }, /* right -> delayed right (no change) */ + { 2, 0, 3 }, /* left & right (middle press) -> pressed middle */ + { 3, 0, 5 }, /* timeout (right press) -> pressed right */ + }, +/* 3 pressed middle */ + { + { -2, 0, 0 }, /* nothing (middle release) -> ground */ + { 0, 0, 7 }, /* left -> released right */ + { 0, 0, 6 }, /* right -> released left */ + { 0, 0, 3 }, /* left & right -> pressed middle (no change) */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 4 pressed left */ + { + { -1, 0, 0 }, /* nothing (left release) -> ground */ + { 0, 0, 4 }, /* left -> pressed left (no change) */ + { -1, 0, 2 }, /* right (left release) -> delayed right */ + { 3, 0, 10 }, /* left & right (right press) -> pressed both */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 5 pressed right */ + { + { -3, 0, 0 }, /* nothing (right release) -> ground */ + { -3, 0, 1 }, /* left (right release) -> delayed left */ + { 0, 0, 5 }, /* right -> pressed right (no change) */ + { 1, 0, 10 }, /* left & right (left press) -> pressed both */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 6 released left */ + { + { -2, 0, 0 }, /* nothing (middle release) -> ground */ + { -2, 0, 1 }, /* left (middle release) -> delayed left */ + { 0, 0, 6 }, /* right -> released left (no change) */ + { 1, 0, 8 }, /* left & right (left press) -> repressed left */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 7 released right */ + { + { -2, 0, 0 }, /* nothing (middle release) -> ground */ + { 0, 0, 7 }, /* left -> released right (no change) */ + { -2, 0, 2 }, /* right (middle release) -> delayed right */ + { 3, 0, 9 }, /* left & right (right press) -> repressed right */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 8 repressed left */ + { + { -2, -1, 0 }, /* nothing (middle release, left release) -> ground */ + { -2, 0, 4 }, /* left (middle release) -> pressed left */ + { -1, 0, 6 }, /* right (left release) -> released left */ + { 0, 0, 8 }, /* left & right -> repressed left (no change) */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 9 repressed right */ + { + { -2, -3, 0 }, /* nothing (middle release, right release) -> ground */ + { -3, 0, 7 }, /* left (right release) -> released right */ + { -2, 0, 5 }, /* right (middle release) -> pressed right */ + { 0, 0, 9 }, /* left & right -> repressed right (no change) */ + { 0, 0, -1 }, /* timeout N/A */ + }, +/* 10 pressed both */ + { + { -1, -3, 0 }, /* nothing (left release, right release) -> ground */ + { -3, 0, 4 }, /* left (right release) -> pressed left */ + { -1, 0, 5 }, /* right (left release) -> pressed right */ + { 0, 0, 10 }, /* left & right -> pressed both (no change) */ + { 0, 0, -1 }, /* timeout N/A */ + }, +}; + + +int +wsmbEmuTimer(InputInfoPtr pInfo) +{ + WSDevicePtr priv = pInfo->private; + int sigstate; + int id; + + sigstate = xf86BlockSIGIO (); + + priv->emulateMB.pending = FALSE; + if ((id = stateTab[priv->emulateMB.state][4][0]) != 0) { + xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0); + priv->emulateMB.state = + stateTab[priv->emulateMB.state][4][2]; + } else { + ErrorF("Got unexpected buttonTimer in state %d\n", + priv->emulateMB.state); + } + + xf86UnblockSIGIO (sigstate); + return 0; +} + + +/** + * Emulate a middle button on button press. + * + * @param code button number (1 for left, 3 for right) + * @param press TRUE if press, FALSE if release. + * + * @return TRUE if event was swallowed by middle mouse button emulation, FALSE + * otherwise. + */ +BOOL +wsmbEmuFilterEvent(InputInfoPtr pInfo, int button, BOOL press) +{ + WSDevicePtr priv = pInfo->private; + int id; + int *btstate; + int ret = FALSE; + + if (!priv->emulateMB.enabled) + return ret; + + if (button == 2) { + wsmbEmuEnable(pInfo, FALSE); + return ret; + } + + /* don't care about other buttons */ + if (button != 1 && button != 3) + return ret; + + btstate = &priv->emulateMB.buttonstate; + if (press) + *btstate |= (button == 1) ? 0x1 : 0x2; + else + *btstate &= (button == 1) ? ~0x1 : ~0x2; + + if ((id = stateTab[priv->emulateMB.state][*btstate][0]) != 0) { + xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0); + ret = TRUE; + } + if ((id = stateTab[priv->emulateMB.state][*btstate][1]) != 0) { + xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0); + ret = TRUE; + } + + priv->emulateMB.state = stateTab[priv->emulateMB.state][*btstate][2]; + + if (stateTab[priv->emulateMB.state][4][0] != 0) { + priv->emulateMB.expires = GetTimeInMillis() + + priv->emulateMB.timeout; + priv->emulateMB.pending = TRUE; + ret = TRUE; + } else { + priv->emulateMB.pending = FALSE; + } + return ret; +} + + +void +wsmbEmuWakeupHandler(pointer data, + int i, + pointer LastSelectMask) +{ + InputInfoPtr pInfo = (InputInfoPtr)data; + WSDevicePtr priv = (WSDevicePtr)pInfo->private; + int ms; + + if (priv->emulateMB.pending) { + ms = priv->emulateMB.expires - GetTimeInMillis(); + if (ms <= 0) + wsmbEmuTimer(pInfo); + } +} + +void +wsmbEmuBlockHandler(pointer data, + struct timeval **waitTime, + pointer LastSelectMask) +{ + InputInfoPtr pInfo = (InputInfoPtr) data; + WSDevicePtr priv= (WSDevicePtr) pInfo->private; + int ms; + + if (priv->emulateMB.pending) { + ms = priv->emulateMB.expires - GetTimeInMillis(); + if (ms <= 0) + ms = 0; + AdjustWaitForDelay (waitTime, ms); + } +} + +void +wsmbEmuPreInit(InputInfoPtr pInfo) +{ + WSDevicePtr priv = (WSDevicePtr)pInfo->private; + priv->emulateMB.enabled = MBEMU_AUTO; + + DBG(1, ErrorF("wsmbEmuPreInit\n")); + if (xf86FindOption(pInfo->options, "Emulate3Buttons")) { + priv->emulateMB.enabled = xf86SetBoolOption(pInfo->options, + "Emulate3Buttons", + MBEMU_ENABLED); + xf86Msg(X_INFO, "%s: Forcing middle mouse button " + "emulation %s.\n", + pInfo->name, (priv->emulateMB.enabled) ? "on" : "off"); + } + + priv->emulateMB.timeout = xf86SetIntOption(pInfo->options, + "Emulate3Timeout", 50); +} + +void +wsmbEmuOn(InputInfoPtr pInfo) +{ + RegisterBlockAndWakeupHandlers(wsmbEmuBlockHandler, + wsmbEmuWakeupHandler, (pointer)pInfo); +} + +void +wsmbEmuFinalize(InputInfoPtr pInfo) +{ + RemoveBlockAndWakeupHandlers(wsmbEmuBlockHandler, + wsmbEmuWakeupHandler, (pointer)pInfo); + +} + +/* Enable/disable middle mouse button emulation. */ +void +wsmbEmuEnable(InputInfoPtr pInfo, BOOL enable) +{ + WSDevicePtr priv = (WSDevicePtr)pInfo->private; + + if (priv->emulateMB.enabled == MBEMU_AUTO) + priv->emulateMB.enabled = enable; +} + + +#ifdef HAVE_PROPERTIES +static int +wsmbEmuSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, + BOOL checkonly) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + WSDevicePtr priv = pInfo->private; + + DBG(1, ErrorF("wsmbEmuSetProperty %s\n", NameForAtom(atom))); + + if (atom == prop_mbemu) { + if (val->format != 8 || val->size != 1 || + val->type != XA_INTEGER) + return BadMatch; + + if (!checkonly) + priv->emulateMB.enabled = *((BOOL*)val->data); + } else if (atom == prop_mbtimeout) { + if (val->format != 32 || val->size != 1 || + val->type != XA_INTEGER) + return BadMatch; + + if (!checkonly) + priv->emulateMB.timeout = *((CARD32*)val->data); + } + + return Success; +} + +/** + * Initialise property for MB emulation on/off. + */ +void +wsmbEmuInitProperty(DeviceIntPtr dev) +{ + InputInfoPtr pInfo = dev->public.devicePrivate; + WSDevicePtr priv = pInfo->private; + int rc; + + DBG(1, ErrorF("wsmbEmuInitProperty\n")); + + if (!dev->button) /* don't init prop for keyboards */ + return; + + prop_mbemu = MakeAtom(WS_PROP_MIDBUTTON, + strlen(WS_PROP_MIDBUTTON), TRUE); + rc = XIChangeDeviceProperty(dev, prop_mbemu, XA_INTEGER, 8, + PropModeReplace, 1, &priv->emulateMB.enabled, FALSE); + if (rc != Success) { + xf86Msg(X_ERROR, "cannot create device property %s: %d\n", + WS_PROP_MIDBUTTON, rc); + return; + } + XISetDevicePropertyDeletable(dev, prop_mbemu, FALSE); + + prop_mbtimeout = MakeAtom(WS_PROP_MIDBUTTON_TIMEOUT, + strlen(WS_PROP_MIDBUTTON_TIMEOUT), + TRUE); + rc = XIChangeDeviceProperty(dev, prop_mbtimeout, + XA_INTEGER, 32, PropModeReplace, 1, + &priv->emulateMB.timeout, FALSE); + + if (rc != Success) { + xf86Msg(X_ERROR, "cannot create device property %s\n", + WS_PROP_MIDBUTTON_TIMEOUT); + return; + } + XISetDevicePropertyDeletable(dev, prop_mbtimeout, FALSE); + + XIRegisterPropertyHandler(dev, wsmbEmuSetProperty, NULL, NULL); +} +#endif @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005-2009 Matthieu Herrb + * Copyright © 2005-2009 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 @@ -36,9 +36,7 @@ #include <mipointer.h> #include <extinit.h> -#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3 -#define HAVE_PROPERTIES 1 -#endif +#include "ws.h" #ifdef HAVE_PROPERTIES #include <X11/Xatom.h> @@ -51,28 +49,6 @@ #include <xserver-properties.h> #endif -#define NAXES 2 /* X and Y axes only */ -#define NBUTTONS 32 /* max theoretical buttons */ -#define DFLTBUTTONS 3 /* default number of buttons */ -#define NUMEVENTS 16 /* max # of ws events to read at once */ - -typedef struct WSDevice { - char *devName; /* device name */ - int type; /* ws device type */ - unsigned int buttons; /* # of buttons */ - unsigned int lastButtons; /* last state of buttons */ - int min_x, max_x, min_y, max_y; /* coord space */ - int swap_axes; - int raw; - int inv_x, inv_y; - int screen_width, screen_height; - int screen_no; - int num, den, threshold; /* relative accel params */ - pointer buffer; - int negativeZ, positiveZ; /* mappings for Z axis */ - int negativeW, positiveW; /* mappings for W axis */ - struct wsmouse_calibcoords coords; /* mirror of the kernel values */ -} WSDeviceRec, *WSDevicePtr; static MODULESETUPPROTO(SetupProc); static void TearDownProc(pointer); @@ -98,6 +74,10 @@ static Atom prop_calibration = 0; static Atom prop_swap = 0; #endif +#ifdef DEBUG +int ws_debug_level = 0; +#endif + static XF86ModuleVersionInfo VersionRec = { "ws", MODULEVENDORSTRING, @@ -129,16 +109,6 @@ InputDriverRec WS = { 0 }; -/* #undef DEBUG */ -#define DEBUG -#undef DBG -static int debug_level = 0; -#ifdef DEBUG -# define DBG(lvl, f) { if ((lvl) <= debug_level) f;} -#else -# define DBG(lvl, f) -#endif - static pointer SetupProc(pointer module, pointer options, int *errmaj, int *errmin) { @@ -180,9 +150,10 @@ wsPreInit(InputDriverPtr drv, IDevPtr dev, int flags) xf86CollectInputOptions(pInfo, NULL, NULL); xf86ProcessCommonOptions(pInfo, pInfo->options); #ifdef DEBUG - debug_level = xf86SetIntOption(pInfo->options, "DebugLevel", - debug_level); - xf86Msg(X_INFO, "%s: debuglevel %d\n", dev->identifier, debug_level); + ws_debug_level = xf86SetIntOption(pInfo->options, "DebugLevel", + ws_debug_level); + xf86Msg(X_INFO, "%s: debuglevel %d\n", dev->identifier, + ws_debug_level); #endif priv->devName = xf86FindOptionValue(pInfo->options, "Device"); if (priv->devName == NULL) { @@ -359,6 +330,8 @@ wsPreInit(InputDriverPtr drv, IDevPtr dev, int flags) wsClose(pInfo); + wsmbEmuPreInit(pInfo); + /* mark the device configured */ pInfo->flags |= XI86_CONFIGURED; return pInfo; @@ -488,6 +461,7 @@ wsDeviceInit(DeviceIntPtr pWS) #ifdef HAVE_PROPERTIES wsInitProperty(pWS); XIRegisterPropertyHandler(pWS, wsSetProperty, NULL, NULL); + wsmbEmuInitProperty(pWS); #endif return Success; } @@ -533,6 +507,7 @@ wsDeviceOn(DeviceIntPtr pWS) return !Success; } xf86AddEnabledDevice(pInfo); + wsmbEmuOn(pInfo); pWS->public.on = TRUE; return Success; } @@ -545,6 +520,7 @@ wsDeviceOff(DeviceIntPtr pWS) 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); @@ -720,14 +696,18 @@ wsSendButtons(InputInfoPtr pInfo, int buttons) WSDevicePtr priv = (WSDevicePtr)pInfo->private; int button, mask; + for (button = 1; button < NBUTTONS; button++) { mask = 1 << (button - 1); if ((mask & priv->lastButtons) != (mask & buttons)) { - xf86PostButtonEvent(pInfo->dev, TRUE, - button, (buttons & mask) != 0, - 0, 0); - DBG(3, ErrorF("post button event %d %d\n", - button, (buttons & mask) != 0)) + if (!wsmbEmuFilterEvent(pInfo, button, + (buttons & mask) != 0)) { + xf86PostButtonEvent(pInfo->dev, TRUE, + button, (buttons & mask) != 0, + 0, 0); + DBG(3, ErrorF("post button event %d %d\n", + button, (buttons & mask) != 0)) + } } } /* for */ priv->lastButtons = buttons; @@ -830,7 +810,7 @@ wsSetProperty(DeviceIntPtr device, Atom atom, XIPropertyValuePtr val, AxisInfoPtr ax = device->valuator->axes, ay = device->valuator->axes + 1; - DBG(1, ErrorF("wsSetProperty\n")); + DBG(1, ErrorF("wsSetProperty %s\n", NameForAtom(atom))); /* Ignore non panel devices */ if (priv->type != WSMOUSE_TYPE_TPANEL) @@ -873,9 +853,7 @@ wsSetProperty(DeviceIntPtr device, Atom atom, XIPropertyValuePtr val, DBG(1, ErrorF("swap_axes %d\n", priv->swap_axes)); need_update++; } - } else { - return BadMatch; - } + } if (need_update) { /* Update the saved values to be restored on device off */ priv->coords.minx = priv->min_x; |