emumb.c revision d6a0ccf1
1/* $OpenBSD: emumb.c,v 1.5 2011/07/17 13:08:38 matthieu Exp $ */ 2/* 3 * Copyright 1990,91 by Thomas Roell, Dinkelscherben, Germany. 4 * Copyright 1993 by David Dawes <dawes@xfree86.org> 5 * Copyright 2002 by SuSE Linux AG, Author: Egbert Eich 6 * Copyright 1994-2002 by The XFree86 Project, Inc. 7 * Copyright 2002 by Paul Elliott 8 * (Ported from xf86-input-mouse, above copyrights taken from there) 9 * Copyright © 2008 University of South Australia 10 * Copyright © 2009 Matthieu Herrb <matthieu@herrb.eu> 11 * 12 * Permission to use, copy, modify, distribute, and sell this software 13 * and its documentation for any purpose is hereby granted without 14 * fee, provided that the above copyright notice appear in all copies 15 * and that both that copyright notice and this permission notice 16 * appear in supporting documentation, and that the name of the authors 17 * not be used in advertising or publicity pertaining to distribution of the 18 * software without specific, written prior permission. The authors make no 19 * representations about the suitability of this software for any 20 * purpose. It is provided "as is" without express or implied 21 * warranty. 22 * 23 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 24 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN 25 * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR 26 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 27 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 28 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 29 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 30 * 31 */ 32 33/* Middle mouse button emulation code. */ 34 35#ifdef HAVE_CONFIG_H 36#include "config.h" 37#endif 38 39#include <sys/types.h> 40#include <sys/time.h> 41 42#include <xorg-server.h> 43#include <X11/Xatom.h> 44#include <xf86.h> 45#include <xf86_OSproc.h> 46#include <X11/extensions/XI.h> 47#include <X11/extensions/XIproto.h> 48#include <xf86Xinput.h> 49#include <exevents.h> 50 51#include <ws-properties.h> 52#include "ws.h" 53 54enum { 55 MBEMU_DISABLED = 0, 56 MBEMU_ENABLED, 57 MBEMU_AUTO 58}; 59 60static Atom prop_mbemu = 0; /* Middle button emulation on/off property */ 61static Atom prop_mbtimeout = 0; /* Middle button timeout property */ 62 63/* 64 * Lets create a simple finite-state machine for 3 button emulation: 65 * 66 * We track buttons 1 and 3 (left and right). There are 11 states: 67 * 0 ground - initial state 68 * 1 delayed left - left pressed, waiting for right 69 * 2 delayed right - right pressed, waiting for left 70 * 3 pressed middle - right and left pressed, emulated middle sent 71 * 4 pressed left - left pressed and sent 72 * 5 pressed right - right pressed and sent 73 * 6 released left - left released after emulated middle 74 * 7 released right - right released after emulated middle 75 * 8 repressed left - left pressed after released left 76 * 9 repressed right - right pressed after released right 77 * 10 pressed both - both pressed, not emulating middle 78 * 79 * At each state, we need handlers for the following events 80 * 0: no buttons down 81 * 1: left button down 82 * 2: right button down 83 * 3: both buttons down 84 * 4: emulate3Timeout passed without a button change 85 * Note that button events are not deltas, they are the set of buttons being 86 * pressed now. It's possible (ie, mouse hardware does it) to go from (eg) 87 * left down to right down without anything in between, so all cases must be 88 * handled. 89 * 90 * a handler consists of three values: 91 * 0: action1 92 * 1: action2 93 * 2: new emulation state 94 * 95 * action > 0: ButtonPress 96 * action = 0: nothing 97 * action < 0: ButtonRelease 98 * 99 * The comment preceeding each section is the current emulation state. 100 * The comments to the right are of the form 101 * <button state> (<events>) -> <new emulation state> 102 * which should be read as 103 * If the buttons are in <button state>, generate <events> then go to 104 * <new emulation state>. 105 */ 106static signed char stateTab[11][5][3] = { 107/* 0 ground */ 108 { 109 { 0, 0, 0 }, /* nothing -> ground (no change) */ 110 { 0, 0, 1 }, /* left -> delayed left */ 111 { 0, 0, 2 }, /* right -> delayed right */ 112 { 2, 0, 3 }, /* left & right (middle press) -> pressed middle */ 113 { 0, 0, -1 } /* timeout N/A */ 114 }, 115/* 1 delayed left */ 116 { 117 { 1, -1, 0 }, /* nothing (left event) -> ground */ 118 { 0, 0, 1 }, /* left -> delayed left (no change) */ 119 { 1, -1, 2 }, /* right (left event) -> delayed right */ 120 { 2, 0, 3 }, /* left & right (middle press) -> pressed middle */ 121 { 1, 0, 4 }, /* timeout (left press) -> pressed left */ 122 }, 123/* 2 delayed right */ 124 { 125 { 3, -3, 0 }, /* nothing (right event) -> ground */ 126 { 3, -3, 1 }, /* left (right event) -> delayed left (no change) */ 127 { 0, 0, 2 }, /* right -> delayed right (no change) */ 128 { 2, 0, 3 }, /* left & right (middle press) -> pressed middle */ 129 { 3, 0, 5 }, /* timeout (right press) -> pressed right */ 130 }, 131/* 3 pressed middle */ 132 { 133 { -2, 0, 0 }, /* nothing (middle release) -> ground */ 134 { 0, 0, 7 }, /* left -> released right */ 135 { 0, 0, 6 }, /* right -> released left */ 136 { 0, 0, 3 }, /* left & right -> pressed middle (no change) */ 137 { 0, 0, -1 }, /* timeout N/A */ 138 }, 139/* 4 pressed left */ 140 { 141 { -1, 0, 0 }, /* nothing (left release) -> ground */ 142 { 0, 0, 4 }, /* left -> pressed left (no change) */ 143 { -1, 0, 2 }, /* right (left release) -> delayed right */ 144 { 3, 0, 10 }, /* left & right (right press) -> pressed both */ 145 { 0, 0, -1 }, /* timeout N/A */ 146 }, 147/* 5 pressed right */ 148 { 149 { -3, 0, 0 }, /* nothing (right release) -> ground */ 150 { -3, 0, 1 }, /* left (right release) -> delayed left */ 151 { 0, 0, 5 }, /* right -> pressed right (no change) */ 152 { 1, 0, 10 }, /* left & right (left press) -> pressed both */ 153 { 0, 0, -1 }, /* timeout N/A */ 154 }, 155/* 6 released left */ 156 { 157 { -2, 0, 0 }, /* nothing (middle release) -> ground */ 158 { -2, 0, 1 }, /* left (middle release) -> delayed left */ 159 { 0, 0, 6 }, /* right -> released left (no change) */ 160 { 1, 0, 8 }, /* left & right (left press) -> repressed left */ 161 { 0, 0, -1 }, /* timeout N/A */ 162 }, 163/* 7 released right */ 164 { 165 { -2, 0, 0 }, /* nothing (middle release) -> ground */ 166 { 0, 0, 7 }, /* left -> released right (no change) */ 167 { -2, 0, 2 }, /* right (middle release) -> delayed right */ 168 { 3, 0, 9 }, /* left & right (right press) -> repressed right */ 169 { 0, 0, -1 }, /* timeout N/A */ 170 }, 171/* 8 repressed left */ 172 { 173 { -2, -1, 0 }, /* nothing (middle release, left release) -> ground */ 174 { -2, 0, 4 }, /* left (middle release) -> pressed left */ 175 { -1, 0, 6 }, /* right (left release) -> released left */ 176 { 0, 0, 8 }, /* left & right -> repressed left (no change) */ 177 { 0, 0, -1 }, /* timeout N/A */ 178 }, 179/* 9 repressed right */ 180 { 181 { -2, -3, 0 }, /* nothing (middle release, right release) -> ground */ 182 { -3, 0, 7 }, /* left (right release) -> released right */ 183 { -2, 0, 5 }, /* right (middle release) -> pressed right */ 184 { 0, 0, 9 }, /* left & right -> repressed right (no change) */ 185 { 0, 0, -1 }, /* timeout N/A */ 186 }, 187/* 10 pressed both */ 188 { 189 { -1, -3, 0 }, /* nothing (left release, right release) -> ground */ 190 { -3, 0, 4 }, /* left (right release) -> pressed left */ 191 { -1, 0, 5 }, /* right (left release) -> pressed right */ 192 { 0, 0, 10 }, /* left & right -> pressed both (no change) */ 193 { 0, 0, -1 }, /* timeout N/A */ 194 }, 195}; 196 197 198int 199wsmbEmuTimer(InputInfoPtr pInfo) 200{ 201 WSDevicePtr priv = pInfo->private; 202 int sigstate; 203 int id; 204 205 sigstate = xf86BlockSIGIO(); 206 207 priv->emulateMB.pending = FALSE; 208 if ((id = stateTab[priv->emulateMB.state][4][0]) != 0) { 209 xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0); 210 priv->emulateMB.state = 211 stateTab[priv->emulateMB.state][4][2]; 212 } else { 213 ErrorF("Got unexpected buttonTimer in state %d\n", 214 priv->emulateMB.state); 215 } 216 217 xf86UnblockSIGIO(sigstate); 218 return 0; 219} 220 221 222/** 223 * Emulate a middle button on button press. 224 * 225 * @param code button number (1 for left, 3 for right) 226 * @param press TRUE if press, FALSE if release. 227 * 228 * @return TRUE if event was swallowed by middle mouse button emulation, FALSE 229 * otherwise. 230 */ 231BOOL 232wsmbEmuFilterEvent(InputInfoPtr pInfo, int button, BOOL press) 233{ 234 WSDevicePtr priv = pInfo->private; 235 int id; 236 int *btstate; 237 int ret = FALSE; 238 239 if (!priv->emulateMB.enabled) 240 return ret; 241 242 if (button == 2) { 243 wsmbEmuEnable(pInfo, FALSE); 244 return ret; 245 } 246 247 /* don't care about other buttons */ 248 if (button != 1 && button != 3) 249 return ret; 250 251 btstate = &priv->emulateMB.buttonstate; 252 if (press) 253 *btstate |= (button == 1) ? 0x1 : 0x2; 254 else 255 *btstate &= (button == 1) ? ~0x1 : ~0x2; 256 257 if ((id = stateTab[priv->emulateMB.state][*btstate][0]) != 0) { 258 xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0); 259 ret = TRUE; 260 } 261 if ((id = stateTab[priv->emulateMB.state][*btstate][1]) != 0) { 262 xf86PostButtonEvent(pInfo->dev, 0, abs(id), (id >= 0), 0, 0); 263 ret = TRUE; 264 } 265 266 priv->emulateMB.state = stateTab[priv->emulateMB.state][*btstate][2]; 267 268 if (stateTab[priv->emulateMB.state][4][0] != 0) { 269 priv->emulateMB.expires = GetTimeInMillis() 270 + priv->emulateMB.timeout; 271 priv->emulateMB.pending = TRUE; 272 ret = TRUE; 273 } else { 274 priv->emulateMB.pending = FALSE; 275 } 276 return ret; 277} 278 279 280void 281wsmbEmuWakeupHandler(pointer data, 282 int i, 283 pointer LastSelectMask) 284{ 285 InputInfoPtr pInfo = (InputInfoPtr)data; 286 WSDevicePtr priv = (WSDevicePtr)pInfo->private; 287 int ms; 288 289 if (priv->emulateMB.pending) { 290 ms = priv->emulateMB.expires - GetTimeInMillis(); 291 if (ms <= 0) 292 wsmbEmuTimer(pInfo); 293 } 294} 295 296void 297wsmbEmuBlockHandler(pointer data, 298 struct timeval **waitTime, 299 pointer LastSelectMask) 300{ 301 InputInfoPtr pInfo = (InputInfoPtr) data; 302 WSDevicePtr priv= (WSDevicePtr) pInfo->private; 303 int ms; 304 305 if (priv->emulateMB.pending) { 306 ms = priv->emulateMB.expires - GetTimeInMillis(); 307 if (ms <= 0) 308 ms = 0; 309 AdjustWaitForDelay(waitTime, ms); 310 } 311} 312 313void 314wsmbEmuPreInit(InputInfoPtr pInfo) 315{ 316 WSDevicePtr priv = (WSDevicePtr)pInfo->private; 317 priv->emulateMB.enabled = MBEMU_AUTO; 318 319 DBG(1, ErrorF("wsmbEmuPreInit\n")); 320 if (xf86FindOption(pInfo->options, "Emulate3Buttons")) { 321 priv->emulateMB.enabled = xf86SetBoolOption(pInfo->options, 322 "Emulate3Buttons", 323 MBEMU_ENABLED); 324 xf86Msg(X_INFO, "%s: Forcing middle mouse button " 325 "emulation %s.\n", 326 pInfo->name, (priv->emulateMB.enabled) ? "on" : "off"); 327 } 328 329 priv->emulateMB.timeout = xf86SetIntOption(pInfo->options, 330 "Emulate3Timeout", 50); 331} 332 333void 334wsmbEmuOn(InputInfoPtr pInfo) 335{ 336 RegisterBlockAndWakeupHandlers(wsmbEmuBlockHandler, 337 wsmbEmuWakeupHandler, (pointer)pInfo); 338} 339 340void 341wsmbEmuFinalize(InputInfoPtr pInfo) 342{ 343 RemoveBlockAndWakeupHandlers(wsmbEmuBlockHandler, 344 wsmbEmuWakeupHandler, (pointer)pInfo); 345 346} 347 348/* Enable/disable middle mouse button emulation. */ 349void 350wsmbEmuEnable(InputInfoPtr pInfo, BOOL enable) 351{ 352 WSDevicePtr priv = (WSDevicePtr)pInfo->private; 353 354 if (priv->emulateMB.enabled == MBEMU_AUTO) 355 priv->emulateMB.enabled = enable; 356} 357 358 359static int 360wsmbEmuSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val, 361 BOOL checkonly) 362{ 363 InputInfoPtr pInfo = dev->public.devicePrivate; 364 WSDevicePtr priv = pInfo->private; 365 366 DBG(1, ErrorF("wsmbEmuSetProperty %s\n", NameForAtom(atom))); 367 368 if (atom == prop_mbemu) { 369 if (val->format != 8 || val->size != 1 || 370 val->type != XA_INTEGER) 371 return BadMatch; 372 373 if (!checkonly) 374 priv->emulateMB.enabled = *((BOOL*)val->data); 375 } else if (atom == prop_mbtimeout) { 376 if (val->format != 32 || val->size != 1 || 377 val->type != XA_INTEGER) 378 return BadMatch; 379 380 if (!checkonly) 381 priv->emulateMB.timeout = *((CARD32*)val->data); 382 } 383 384 return Success; 385} 386 387/** 388 * Initialise property for MB emulation on/off. 389 */ 390void 391wsmbEmuInitProperty(DeviceIntPtr dev) 392{ 393 InputInfoPtr pInfo = dev->public.devicePrivate; 394 WSDevicePtr priv = pInfo->private; 395 int rc; 396 397 DBG(1, ErrorF("wsmbEmuInitProperty\n")); 398 399 if (!dev->button) /* don't init prop for keyboards */ 400 return; 401 402 prop_mbemu = MakeAtom(WS_PROP_MIDBUTTON, 403 strlen(WS_PROP_MIDBUTTON), TRUE); 404 rc = XIChangeDeviceProperty(dev, prop_mbemu, XA_INTEGER, 8, 405 PropModeReplace, 1, &priv->emulateMB.enabled, FALSE); 406 if (rc != Success) { 407 xf86Msg(X_ERROR, "cannot create device property %s: %d\n", 408 WS_PROP_MIDBUTTON, rc); 409 return; 410 } 411 XISetDevicePropertyDeletable(dev, prop_mbemu, FALSE); 412 413 prop_mbtimeout = MakeAtom(WS_PROP_MIDBUTTON_TIMEOUT, 414 strlen(WS_PROP_MIDBUTTON_TIMEOUT), 415 TRUE); 416 rc = XIChangeDeviceProperty(dev, prop_mbtimeout, 417 XA_INTEGER, 32, PropModeReplace, 1, 418 &priv->emulateMB.timeout, FALSE); 419 420 if (rc != Success) { 421 xf86Msg(X_ERROR, "cannot create device property %s\n", 422 WS_PROP_MIDBUTTON_TIMEOUT); 423 return; 424 } 425 XISetDevicePropertyDeletable(dev, prop_mbtimeout, FALSE); 426 427 XIRegisterPropertyHandler(dev, wsmbEmuSetProperty, NULL, NULL); 428} 429