/* $OpenBSD: emuwheel.c,v 1.4 2018/06/18 20:07:30 bru Exp $ */
/*
* 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 by Chris Salch
* Copyright © 2008 Red Hat, Inc.
*
* 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.
*
*/
/* Mouse wheel emulation code. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/time.h>
#include <xorg-server.h>
#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"
static Atom prop_wheel_emu;
static Atom prop_wheel_axismap;
static Atom prop_wheel_inertia;
static Atom prop_wheel_timeout;
static Atom prop_wheel_button;
static int wsWheelEmuInertia(InputInfoPtr, WheelAxisPtr, int);
static int wsWheelEmuSetProperty(DeviceIntPtr, Atom, XIPropertyValuePtr, BOOL);
BOOL
wsWheelEmuFilterButton(InputInfoPtr pInfo, unsigned int button, int press)
{
WSDevicePtr priv = (WSDevicePtr)pInfo->private;
int ms;
if (!priv->emulateWheel.enabled)
return FALSE;
if (priv->emulateWheel.button == button) {
priv->emulateWheel.button_state = press;
if (press) {
priv->emulateWheel.expires = GetTimeInMillis() +
priv->emulateWheel.timeout;
} else {
ms = priv->emulateWheel.expires - GetTimeInMillis();
if (ms > 0) {
/*
* If the button is released early enough emit
* the button press/release events
*/
wsButtonClicks(pInfo, button, 1);
}
}
return TRUE;
}
return FALSE;
}
BOOL
wsWheelEmuFilterMotion(InputInfoPtr pInfo, int dx, int dy)
{
WSDevicePtr priv = (WSDevicePtr)pInfo->private;
WheelAxisPtr pAxis = NULL, pOtherAxis = NULL;
int value;
if (!priv->emulateWheel.enabled)
return FALSE;
/*
* Handle our motion events if the emuWheel button is pressed.
* Wheel button of 0 means always emulate wheel.
*/
if (priv->emulateWheel.button_state || priv->emulateWheel.button == 0) {
if (priv->emulateWheel.button) {
int ms = priv->emulateWheel.expires - GetTimeInMillis();
if (ms > 0)
return TRUE;
}
if (abs(dx) > abs(dy)) {
pAxis = &(priv->emulateWheel.X);
pOtherAxis = &(priv->emulateWheel.Y);
value = dx;
} else if (dy) {
pAxis = &(priv->emulateWheel.Y);
pOtherAxis = &(priv->emulateWheel.X);
value = dy;
} else
return FALSE;
/*
* Reset the inertia of the other axis when a scroll event
* was sent to avoid the buildup of erroneous scroll events if the
* user doesn't move in a perfectly straight line.
*/
if (pAxis) {
if (wsWheelEmuInertia(pInfo, pAxis, value))
pOtherAxis->traveled_distance = 0;
}
return TRUE;
}
return FALSE;
}
static int
wsWheelEmuInertia(InputInfoPtr pInfo, WheelAxisPtr axis, int value)
{
WSDevicePtr priv = (WSDevicePtr)pInfo->private;
int button, inertia;
int rc = 0;
if (axis->negative == WS_NOMAP)
return rc;
axis->traveled_distance += value;
if (axis->traveled_distance < 0) {
button = axis->negative;
inertia = -priv->emulateWheel.inertia;
} else {
button = axis->positive;
inertia = priv->emulateWheel.inertia;
}
while (abs(axis->traveled_distance) > priv->emulateWheel.inertia) {
axis->traveled_distance -= inertia;
wsButtonClicks(pInfo, button, 1);
rc++;
}
return rc;
}
void
wsWheelEmuPreInit(InputInfoPtr pInfo)
{
WSDevicePtr priv = (WSDevicePtr)pInfo->private;
int button, inertia, timeout;
priv->emulateWheel.enabled = xf86SetBoolOption(pInfo->options,
"EmulateWheel", FALSE);
button = xf86SetIntOption(pInfo->options, "EmulateWheelButton", 4);
if (button < 0 || button > NBUTTONS) {
xf86IDrvMsg(pInfo, X_WARNING,
"Invalid EmulateWheelButton value: %d\n", button);
xf86IDrvMsg(pInfo, X_WARNING, "Wheel emulation disabled\n");
priv->emulateWheel.enabled = FALSE;
button = 4;
}
priv->emulateWheel.button = button;
inertia = xf86SetIntOption(pInfo->options, "EmulateWheelInertia", 10);
if (inertia <= 0) {
xf86IDrvMsg(pInfo, X_WARNING,
"Invalid EmulateWheelInertia value: %d\n", inertia);
xf86IDrvMsg(pInfo, X_WARNING, "Using built-in inertia value\n");
inertia = 10;
}
priv->emulateWheel.inertia = inertia;
timeout = xf86SetIntOption(pInfo->options, "EmulateWheelTimeout", 200);
if (timeout < 0) {
xf86IDrvMsg(pInfo, X_WARNING,
"Invalid EmulateWheelTimeout value: %d\n", timeout);
xf86IDrvMsg(pInfo, X_WARNING, "Using built-in timeout value\n");
timeout = 200;
}
priv->emulateWheel.timeout = timeout;
wsWheelHandleButtonMap(pInfo, &(priv->emulateWheel.Y),
"YAxisMapping", "4 5");
wsWheelHandleButtonMap(pInfo, &(priv->emulateWheel.X),
"XAxisMapping", NULL);
}
static int
wsWheelEmuSetProperty(DeviceIntPtr dev, Atom atom, XIPropertyValuePtr val,
BOOL checkonly)
{
InputInfoPtr pInfo = (InputInfoPtr)dev->public.devicePrivate;
WSDevicePtr priv = (WSDevicePtr)pInfo->private;
if (atom == prop_wheel_emu) {
if (val->format != 8 || val->size != 1 ||
val->type != XA_INTEGER)
return BadMatch;
if (!checkonly)
priv->emulateWheel.enabled = *((BOOL*)val->data);
} else if (atom == prop_wheel_button) {
int button;
if (val->format != 8 || val->size != 1 ||
val->type != XA_INTEGER)
return BadMatch;
button = *((CARD8*)val->data);
if (button < 0 || button > NBUTTONS)
return BadValue;
if (!checkonly)
priv->emulateWheel.button = button;
} else if (atom == prop_wheel_axismap) {
int x_negative, x_positive;
int y_negative, y_positive;
if (val->format != 8 || val->size != 4 ||
val->type != XA_INTEGER)
return BadMatch;
x_negative = *((CARD8*)val->data);
x_positive = *(((CARD8*)val->data) + 1);
y_negative = *(((CARD8*)val->data) + 2);
y_positive = *(((CARD8*)val->data) + 3);
if (x_negative < 0 || x_negative > NBUTTONS ||
x_positive < 0 || x_positive > NBUTTONS ||
y_negative < 0 || y_negative > NBUTTONS ||
y_positive < 0 || y_positive > NBUTTONS)
return BadValue;
if ((x_negative == WS_NOMAP && x_positive != WS_NOMAP) ||
(x_negative != WS_NOMAP && x_positive == WS_NOMAP) ||
(y_negative == WS_NOMAP && y_positive != WS_NOMAP) ||
(y_negative != WS_NOMAP && y_positive == WS_NOMAP))
return BadValue;
if (!checkonly) {
priv->emulateWheel.X.negative = x_negative;
priv->emulateWheel.X.positive = x_positive;
priv->emulateWheel.Y.negative = y_negative;
priv->emulateWheel.Y.positive = y_positive;
}
} else if (atom == prop_wheel_inertia) {
int inertia;
if (val->format != 16 || val->size != 1 ||
val->type != XA_INTEGER)
return BadMatch;
inertia = *((CARD16*)val->data);
if (inertia <= 0)
return BadValue;
if (!checkonly)
priv->emulateWheel.inertia = inertia;
} else if (atom == prop_wheel_timeout) {
int timeout;
if (val->format != 32 || val->size != 1 ||
val->type != XA_INTEGER)
return BadMatch;
timeout = *((CARD32*)val->data);
if (timeout < 0)
return BadValue;
if (!checkonly)
priv->emulateWheel.timeout = timeout;
}
return Success;
}
void
wsWheelEmuInitProperty(DeviceIntPtr dev)
{
InputInfoPtr pInfo = (InputInfoPtr)dev->public.devicePrivate;
WSDevicePtr priv = (WSDevicePtr)pInfo->private;
char vals[4];
int rc;
prop_wheel_emu = MakeAtom(WS_PROP_WHEEL, strlen(WS_PROP_WHEEL), TRUE);
rc = XIChangeDeviceProperty(dev, prop_wheel_emu, XA_INTEGER, 8,
PropModeReplace, 1, &priv->emulateWheel.enabled, FALSE);
if (rc != Success) {
xf86IDrvMsg(pInfo, X_ERROR,
"cannot create device property %s: %d\n",
WS_PROP_WHEEL, rc);
return;
}
XISetDevicePropertyDeletable(dev, prop_wheel_emu, FALSE);
vals[0] = priv->emulateWheel.X.negative;
vals[1] = priv->emulateWheel.X.positive;
vals[2] = priv->emulateWheel.Y.negative;
vals[3] = priv->emulateWheel.Y.positive;
prop_wheel_axismap = MakeAtom(WS_PROP_WHEEL_AXES,
strlen(WS_PROP_WHEEL_AXES), TRUE);
rc = XIChangeDeviceProperty(dev, prop_wheel_axismap, XA_INTEGER, 8,
PropModeReplace, 4, vals, FALSE);
if (rc != Success) {
xf86IDrvMsg(pInfo, X_ERROR,
"cannot create device property %s: %d\n",
WS_PROP_WHEEL_AXES, rc);
return;
}
XISetDevicePropertyDeletable(dev, prop_wheel_axismap, FALSE);
prop_wheel_inertia = MakeAtom(WS_PROP_WHEEL_INERTIA,
strlen(WS_PROP_WHEEL_INERTIA), TRUE);
rc = XIChangeDeviceProperty(dev, prop_wheel_inertia, XA_INTEGER, 16,
PropModeReplace, 1, &priv->emulateWheel.inertia, FALSE);
if (rc != Success) {
xf86IDrvMsg(pInfo, X_ERROR,
"cannot create device property %s: %d\n",
WS_PROP_WHEEL_INERTIA, rc);
return;
}
XISetDevicePropertyDeletable(dev, prop_wheel_inertia, FALSE);
prop_wheel_timeout = MakeAtom(WS_PROP_WHEEL_TIMEOUT,
strlen(WS_PROP_WHEEL_TIMEOUT), TRUE);
rc = XIChangeDeviceProperty(dev, prop_wheel_timeout, XA_INTEGER, 32,
PropModeReplace, 1, &priv->emulateWheel.timeout, FALSE);
if (rc != Success) {
xf86IDrvMsg(pInfo, X_ERROR,
"cannot create device property %s: %d\n",
WS_PROP_WHEEL_TIMEOUT, rc);
return;
}
XISetDevicePropertyDeletable(dev, prop_wheel_timeout, FALSE);
prop_wheel_button = MakeAtom(WS_PROP_WHEEL_BUTTON,
strlen(WS_PROP_WHEEL_BUTTON), TRUE);
rc = XIChangeDeviceProperty(dev, prop_wheel_button, XA_INTEGER, 8,
PropModeReplace, 1, &priv->emulateWheel.button, FALSE);
if (rc != Success) {
xf86IDrvMsg(pInfo, X_ERROR,
"cannot create device property %s: %d\n",
WS_PROP_WHEEL_BUTTON, rc);
return;
}
XISetDevicePropertyDeletable(dev, prop_wheel_button, FALSE);
XIRegisterPropertyHandler(dev, wsWheelEmuSetProperty, NULL, NULL);
}