summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Hutterer <peter@cs.unisa.edu.au>2008-06-10 15:55:40 +0930
committerPeter Hutterer <peter@cs.unisa.edu.au>2008-06-10 22:46:20 +0930
commite8887435ac065ec3071b2d8bf0895e8cb196ec3d (patch)
tree22fea9071a9c4ca8e6c61e6bffbe463508365899
parentb0f6987ee6b133e28f3af18da62cfb5ca79fbe07 (diff)
Enable middle-mouse button emulation.
Ported from xf86-input-mouse, with a few cleanups.
-rw-r--r--man/evdev.man13
-rw-r--r--src/Makefile.am4
-rw-r--r--src/emuMB.c308
-rw-r--r--src/evdev.c41
-rw-r--r--src/evdev.h80
5 files changed, 418 insertions, 28 deletions
diff --git a/man/evdev.man b/man/evdev.man
index dbe94f4..b8807f0 100644
--- a/man/evdev.man
+++ b/man/evdev.man
@@ -10,6 +10,8 @@ evdev \- Generic Linux input driver
.B " Driver \*qevdev\*q"
.BI " Option \*qDevice\*q \*q" devpath \*q
.BI " Option \*qPath\*q \*q" path \*q
+.BI " Option \*qEmulate3Buttons\*q \*q" True \*q
+.BI " Option \*qEmulate3Timeout\*q \*q" 50 \*q
\ \ ...
.B EndSection
.fi
@@ -54,6 +56,17 @@ generally be of the form \*q/dev/input/by-path/xyz\*q, where xyz includes the
name of the device. The mapping from device node to hardware is
system-dependent. This option has precedence over the \*qDevice\*q option but
one of \*qPath\*q or \*qDevice\*q must be given.
+.TP 7
+.BI "Option \*qEmulate3Buttons\*q \*q" boolean \*q
+Enable/disable the emulation of the third (middle) mouse button for mice
+which only have two physical buttons. The third button is emulated by
+pressing both buttons simultaneously. Default: on, unless the device reports
+the presence of a physical button 3.
+.TP 7
+.BI "Option \*qEmulate3Timeout\*q \*q" integer \*q
+Sets the timeout (in milliseconds) that the driver waits before deciding
+if two buttons where pressed "simultaneously" when 3 button emulation is
+enabled. Default: 50.
.SH AUTHORS
Kristian Høgsberg.
.SH "SEE ALSO"
diff --git a/src/Makefile.am b/src/Makefile.am
index 2221bbc..d0b9b63 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -28,4 +28,6 @@
@DRIVER_NAME@_drv_la_LDFLAGS = -module -avoid-version
@DRIVER_NAME@_drv_ladir = @inputdir@
-@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..2855632
--- /dev/null
+++ b/src/emuMB.c
@@ -0,0 +1,308 @@
+/*
+ * 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
+ *
+ * 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 Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission. Red
+ * Hat makes 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 "evdev.h"
+
+/*
+ * 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
+EvdevMBEmuTimer(InputInfoPtr pInfo)
+{
+ EvdevPtr pEvdev = pInfo->private;
+ int sigstate;
+ int id;
+
+ sigstate = xf86BlockSIGIO ();
+
+ pEvdev->emulateMB.pending = FALSE;
+ if ((id = stateTab[pEvdev->emulateMB.state][4][0]) != 0) {
+ xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0);
+ pEvdev->emulateMB.state =
+ stateTab[pEvdev->emulateMB.state][4][2];
+ } else {
+ ErrorF("Got unexpected buttonTimer in state %d\n",
+ pEvdev->emulateMB.state);
+ }
+
+ xf86UnblockSIGIO (sigstate);
+ return 0;
+}
+
+
+/**
+ * Emulate a middle button on button press.
+ *
+ * @param code Evdev event code (BTN_LEFT or BTN_RIGHT)
+ * @param press TRUE if press, FALSE if release.
+ *
+ * @return TRUE if event was swallowed by middle mouse button emulation, FALSE
+ * otherwise.
+ */
+BOOL
+EvdevMBEmuFilterEvent(InputInfoPtr pInfo, int code, BOOL press)
+{
+ EvdevPtr pEvdev = pInfo->private;
+ int id;
+ int *btstate;
+ int ret = FALSE;
+
+ if (!pEvdev->emulateMB.enabled)
+ return ret;
+
+ /* don't care about other buttons */
+ if (code != BTN_LEFT && code != BTN_RIGHT)
+ return ret;
+
+ btstate = &pEvdev->emulateMB.buttonstate;
+ if (press)
+ *btstate |= (code == BTN_LEFT) ? 0x1 : 0x2;
+ else
+ *btstate &= (code == BTN_LEFT) ? ~0x1 : ~0x2;
+
+ if ((id = stateTab[pEvdev->emulateMB.state][*btstate][0]) != 0)
+ {
+ xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0);
+ ret = TRUE;
+ }
+ if ((id = stateTab[pEvdev->emulateMB.state][*btstate][1]) != 0)
+ {
+ xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0);
+ ret = TRUE;
+ }
+
+ pEvdev->emulateMB.state =
+ stateTab[pEvdev->emulateMB.state][*btstate][2];
+
+ if (stateTab[pEvdev->emulateMB.state][4][0] != 0) {
+ pEvdev->emulateMB.expires = GetTimeInMillis () + pEvdev->emulateMB.timeout;
+ pEvdev->emulateMB.pending = TRUE;
+ ret = TRUE;
+ } else {
+ pEvdev->emulateMB.pending = FALSE;
+ }
+
+ return ret;
+}
+
+
+void EvdevMBEmuWakeupHandler(pointer data,
+ int i,
+ pointer LastSelectMask)
+{
+ InputInfoPtr pInfo = (InputInfoPtr)data;
+ EvdevPtr pEvdev = (EvdevPtr)pInfo->private;
+ int ms;
+
+ if (pEvdev->emulateMB.pending)
+ {
+ ms = pEvdev->emulateMB.expires - GetTimeInMillis();
+ if (ms <= 0)
+ EvdevMBEmuTimer(pInfo);
+ }
+}
+
+void EvdevMBEmuBlockHandler(pointer data,
+ struct timeval **waitTime,
+ pointer LastSelectMask)
+{
+ InputInfoPtr pInfo = (InputInfoPtr) data;
+ EvdevPtr pEvdev= (EvdevPtr) pInfo->private;
+ int ms;
+
+ if (pEvdev->emulateMB.pending)
+ {
+ ms = pEvdev->emulateMB.expires - GetTimeInMillis ();
+ if (ms <= 0)
+ ms = 0;
+ AdjustWaitForDelay (waitTime, ms);
+ }
+}
+
+void
+EvdevMBEmuPreInit(InputInfoPtr pInfo)
+{
+ EvdevPtr pEvdev = (EvdevPtr)pInfo->private;
+
+ pEvdev->emulateMB.enabled = xf86SetBoolOption(pInfo->options,
+ "Emulate3Buttons", TRUE);
+ pEvdev->emulateMB.timeout = xf86SetIntOption(pInfo->options,
+ "Emulate3Timeout", 50);
+ RegisterBlockAndWakeupHandlers (EvdevMBEmuBlockHandler,
+ EvdevMBEmuWakeupHandler,
+ (pointer)pInfo);
+
+}
+
+/* Enable/disable middle mouse button emulation. */
+void
+EvdevMBEmuEnable(InputInfoPtr pInfo, BOOL enable)
+{
+ EvdevPtr pEvdev = (EvdevPtr)pInfo->private;
+ pEvdev->emulateMB.enabled = enable;
+}
diff --git a/src/evdev.c b/src/evdev.c
index 2f093c4..27d75dc 100644
--- a/src/evdev.c
+++ b/src/evdev.c
@@ -33,7 +33,6 @@
#include <X11/XF86keysym.h>
#include <X11/extensions/XIproto.h>
-#include <linux/input.h>
#include <unistd.h>
#include <misc.h>
@@ -44,10 +43,7 @@
#include <exevents.h>
#include <mipointer.h>
-#if defined(XKB)
-/* XXX VERY WRONG. this is a client side header. */
-#include <X11/extensions/XKBstr.h>
-#endif
+#include "evdev.h"
#include <xf86Module.h>
@@ -91,25 +87,6 @@
#define MODEFLAG 8
#define COMPOSEFLAG 16
-typedef struct {
- int kernel24;
- int screen;
- int min_x, min_y, max_x, max_y;
- int abs_x, abs_y, old_x, old_y;
- int flags;
- int tool;
-
- /* XKB stuff has to be per-device rather than per-driver */
- int noXkb;
-#ifdef XKB
- char *xkb_rules;
- char *xkb_model;
- char *xkb_layout;
- char *xkb_variant;
- char *xkb_options;
- XkbComponentNamesRec xkbnames;
-#endif
-} EvdevRec, *EvdevPtr;
static const char *evdevDefaults[] = {
"XkbRules", "base",
@@ -241,10 +218,12 @@ EvdevReadInput(InputInfoPtr pInfo)
switch (ev.code) {
/* swap here, pretend we're an X-conformant device. */
case BTN_LEFT:
- xf86PostButtonEvent(pInfo->dev, 0, 1, value, 0, 0);
+ if (!EvdevMBEmuFilterEvent(pInfo, ev.code, value))
+ xf86PostButtonEvent(pInfo->dev, 0, 1, value, 0, 0);
break;
case BTN_RIGHT:
- xf86PostButtonEvent(pInfo->dev, 0, 3, value, 0, 0);
+ if (!EvdevMBEmuFilterEvent(pInfo, ev.code, value))
+ xf86PostButtonEvent(pInfo->dev, 0, 3, value, 0, 0);
break;
case BTN_MIDDLE:
xf86PostButtonEvent(pInfo->dev, 0, 2, value, 0, 0);
@@ -964,6 +943,12 @@ EvdevProbe(InputInfoPtr pInfo)
has_buttons = TRUE;
}
+ if (TestBit(BTN_MIDDLE, key_bitmask)) {
+ xf86Msg(X_INFO, "%s: Found middle button. Disabling emulation.\n",
+ pInfo->name);
+ EvdevMBEmuEnable(pInfo, FALSE);
+ }
+
for (i = 0; i < BTN_MISC; i++)
if (TestBit(i, key_bitmask))
break;
@@ -1044,6 +1029,8 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
*/
pEvdev->tool = 1;
+ EvdevMBEmuPreInit(pInfo);
+
device = xf86CheckStrOption(dev->commonOptions, "Path", NULL);
if (!device)
device = xf86CheckStrOption(dev->commonOptions, "Device", NULL);
@@ -1052,7 +1039,7 @@ EvdevPreInit(InputDriverPtr drv, IDevPtr dev, int flags)
xf86DeleteInput(pInfo, 0);
return NULL;
}
-
+
xf86Msg(deviceFrom, "%s: Device: \"%s\"\n", pInfo->name, device);
do {
pInfo->fd = open(device, O_RDWR, 0);
diff --git a/src/evdev.h b/src/evdev.h
new file mode 100644
index 0000000..9b88e16
--- /dev/null
+++ b/src/evdev.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright © 2004-2008 Red Hat, Inc.
+ * Copyright © 2008 University of South Australia
+ *
+ * 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 Red Hat
+ * not be used in advertising or publicity pertaining to distribution
+ * of the software without specific, written prior permission. Red
+ * Hat makes 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.
+ *
+ * Authors:
+ * Kristian Høgsberg (krh@redhat.com)
+ * Adam Jackson (ajax@redhat.com)
+ * Peter Hutterer (peter@cs.unisa.edu.au)
+ */
+
+#ifndef EVDEV_H
+#define EVDEV_H
+
+#include <linux/input.h>
+
+#include <xf86Xinput.h>
+#include <xf86_OSproc.h>
+
+#if defined(XKB)
+/* XXX VERY WRONG. this is a client side header. */
+#include <X11/extensions/XKBstr.h>
+#endif
+
+typedef struct {
+ int kernel24;
+ int screen;
+ int min_x, min_y, max_x, max_y;
+ int abs_x, abs_y, old_x, old_y;
+ int flags;
+ int tool;
+
+ /* XKB stuff has to be per-device rather than per-driver */
+ int noXkb;
+#ifdef XKB
+ char *xkb_rules;
+ char *xkb_model;
+ char *xkb_layout;
+ char *xkb_variant;
+ char *xkb_options;
+ XkbComponentNamesRec xkbnames;
+#endif
+ /* Middle mouse button emulation */
+ struct {
+ BOOL enabled;
+ BOOL pending; /* timer waiting? */
+ int buttonstate; /* phys. button state */
+ int state; /* state machine (see bt3emu.c) */
+ Time expires; /* time of expiry */
+ Time timeout;
+ } emulateMB;
+} EvdevRec, *EvdevPtr;
+
+/* Middle Button emulation */
+int EvdevMBEmuTimer(InputInfoPtr);
+BOOL EvdevMBEmuFilterEvent(InputInfoPtr, int, BOOL);
+void EvdevMBEmuWakeupHandler(pointer, int, pointer);
+void EvdevMBEmuBlockHandler(pointer, struct timeval**, pointer);
+void EvdevMBEmuPreInit(InputInfoPtr);
+void EvdevMBEmuEnable(InputInfoPtr, BOOL);
+
+#endif