xenocara/driver/xf86-input-joystick/src/jstk_axis.c

609 lines
20 KiB
C

/*
* Copyright 2007-2008 by Sascha Hlusiak. <saschahlusiak@freedesktop.org>
*
* 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 Sascha Hlusiak not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Sascha Hlusiak makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* SASCHA HLUSIAK DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL SASCHA HLUSIAK 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.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <xorg-server.h>
#include <xf86Xinput.h>
#include <xf86_OSproc.h>
#include <math.h>
#include <stdlib.h>
#include "jstk.h"
#include "jstk_axis.h"
#include "jstk_key.h"
/***********************************************************************
*
* jstkAxisTimer --
*
* The timer that will generate PointerMove-events. Checks every axis
* and every button for it's mapping.
* Return 0, when timer can be stopped, because there is no active
* movement
*
***********************************************************************
*/
static CARD32
jstkAxisTimer(OsTimerPtr timer,
CARD32 atime,
pointer arg)
{
#define NEXTTIMER 15
DeviceIntPtr device = (DeviceIntPtr)arg;
InputInfoPtr pInfo = device->public.devicePrivate;
JoystickDevPtr priv = pInfo->private;
int sigstate, i;
int nexttimer;
int movex,movey,movezx,movezy;
nexttimer = 0;
movex = movey = movezx = movezy = 0;
sigstate = xf86BlockSIGIO();
for (i=0; i<MAXAXES; i++) if ((priv->axis[i].value != 0) &&
(priv->axis[i].type != JSTK_TYPE_NONE)) {
float p1 = 0.0f; /* Pixels to move cursor */
float p2 = 0.0f; /* Pixels to scroll */
float scale;
AXIS *axis;
axis = &priv->axis[i];
nexttimer = NEXTTIMER;
if (priv->axis[i].type == JSTK_TYPE_BYVALUE) {
/* Calculate scale value, so we get a range from 0 to 32768 */
scale = (32768.0f / (float)(32768 - axis->deadzone));
/* NOTE: joysticks with a rectangular field have a
* corner position of (32768,32768), joysticks with a
* circular field have (23170,23170).
*
* make sure that diagonal movement feels fast. either:
* 1) linear
*
* f(32768) ~= f(23170) + f(23170)
* f(32768) ~= a * f(23170)
* a = 2.0
*
* on circular joysticks, the time needed for xy movement is
* exactly the time needed for x + the time for y separately.
* absolute diagonal travel speed (in cm/s) is 0.707 times as fast,
* which feels pretty slow.
*
* on square joysticks, diagonal travel speed is always 1.41 times
* faster than orthogonal travel speed. time needed for diagonal
* movement is always 0.5 times as long as for orthogonal movement.
*
* the value of a = 2.0 results in a nice, non-linear acceleration.
*
* or
* 2) trigonometric
*
* f(32768) ~= sqrt(f(23170)^2 + f(23170)^2))
* f(32768) ~= a * f(23170)
* a = 1.414
*
* on circular joysticks, the absolute pointer travel speed
* (in cm/s) is now the same for both linear and diagonal movement,
* which feels natural. moving diagonally takes 0.707 times the time
* of moving orthogonally.
*
* on square joysticks, values are as in 1)
*
* the value of a = 1.414 results in linear acceleration, which feels
* too slow.
*
* to maintain non-linear acceleration, make sure that:
*
* a >>= 1.414
*
* the following formula achieves results in between,
* so it should feel natural on both devices while maintaining a
* nice acceleration:
*
* f(32768) ~= 1.620 * f(23170)
*
* TODO: make this simpler by using only values -1.0..1.0 and
* provide acceleration graphs.
*/
/* How many pixels should this axis move the cursor */
p1 = (pow((abs((float)axis->value) - (float)axis->deadzone) *
scale / 23, 1.4f) + 100.0f) *
((float)NEXTTIMER / 40000.0f);
/* How many "pixels" should this axis scroll */
p2 = ((pow((abs((float)axis->value) - (float)axis->deadzone) *
scale / 1000.0f, 2.5f)) + 200.0f) *
((float)NEXTTIMER / 200000.0f);
} else if (axis->type == JSTK_TYPE_ACCELERATED) {
/* Stop to accelerate at a certain speed */
if (axis->currentspeed < 100.0f)
axis->currentspeed = (axis->currentspeed + 3.0f) * 1.07f - 3.0f;
p1 = axis->currentspeed * (float)NEXTTIMER / 180.0f;
p2 = p1 / 8.0f;
}
if (axis->value < 0) {
p1 = -p1;
p2 = -p2;
}
p1 *= axis->amplify * priv->amplify;
p2 *= axis->amplify * priv->amplify;
/* Apply movement to global amount of pixels to move */
switch (axis->mapping) {
case JSTK_MAPPING_X:
case JSTK_MAPPING_Y:
axis->subpixel += p1;
break;
case JSTK_MAPPING_ZX:
case JSTK_MAPPING_ZY:
case JSTK_MAPPING_KEY:
axis->subpixel += p2;
break;
default:
break;
}
if ((int)axis->subpixel != 0) {
switch (axis->mapping) {
case JSTK_MAPPING_X:
movex += (int)axis->subpixel;
break;
case JSTK_MAPPING_Y:
movey += (int)axis->subpixel;
break;
case JSTK_MAPPING_ZX:
movezx += (int)axis->subpixel;
break;
case JSTK_MAPPING_ZY:
movezy += (int)axis->subpixel;
break;
case JSTK_MAPPING_KEY: if ((priv->keys_enabled == TRUE) &&
(priv->axis[i].type == JSTK_TYPE_BYVALUE)) {
int num;
num = abs((int)axis->subpixel);
if ((int)axis->subpixel < 0) {
for (i=0; i<num; i++) {
jstkGenerateKeys(priv->keyboard_device, axis->keys_low, 1);
jstkGenerateKeys(priv->keyboard_device, axis->keys_low, 0);
}
} else {
for (i=0; i<num; i++) {
jstkGenerateKeys(priv->keyboard_device, axis->keys_high, 1);
jstkGenerateKeys(priv->keyboard_device, axis->keys_high, 0);
}
}
break;
}
default:
break;
}
axis->subpixel = axis->subpixel - (int)axis->subpixel;
}
}
for (i=0; i<MAXBUTTONS; i++) if (priv->button[i].pressed == 1) {
float p1;
float p2;
if (priv->button[i].currentspeed < 100.0f)
priv->button[i].currentspeed =
(priv->button[i].currentspeed + 3.0f) * 1.07f - 3.0f;
p1 = priv->button[i].currentspeed * (float)NEXTTIMER / 180.0f *
priv->button[i].amplify;
p1 *= priv->amplify;
p2 = p1 / 8.0f;
/* Apply movement to amount of pixels to move */
switch (priv->button[i].mapping) {
case JSTK_MAPPING_X:
case JSTK_MAPPING_Y:
priv->button[i].subpixel += p1;
nexttimer = NEXTTIMER;
break;
case JSTK_MAPPING_ZX:
case JSTK_MAPPING_ZY:
priv->button[i].subpixel += p2;
nexttimer = NEXTTIMER;
break;
default:
break;
}
if ((int)priv->button[i].subpixel != 0) {
switch (priv->button[i].mapping) {
case JSTK_MAPPING_X:
movex += (int)priv->button[i].subpixel;
break;
case JSTK_MAPPING_Y:
movey += (int)priv->button[i].subpixel;
break;
case JSTK_MAPPING_ZX:
movezx += (int)priv->button[i].subpixel;
break;
case JSTK_MAPPING_ZY:
movezy += (int)priv->button[i].subpixel;
break;
default:
break;
}
priv->button[i].subpixel -= (int)priv->button[i].subpixel;
}
}
/* Actually move the cursor, if there is enough movement in the buffer */
if ((movex != 0)||(movey != 0)) {
xf86PostMotionEvent(device, 0, 0, 2, movex, movey);
}
/* Generate scrolling events */
while (movezy >= 1) { /* down */
xf86PostButtonEvent(device, 0, 5,
1, 0, 0);
xf86PostButtonEvent(device, 0, 5,
0, 0, 0);
movezy -= 1;
}
while (movezy <= -1) { /* up */
xf86PostButtonEvent(device, 0, 4,
1, 0, 0);
xf86PostButtonEvent(device, 0, 4,
0, 0, 0);
movezy += 1;
}
while (movezx >= 1) { /* right */
xf86PostButtonEvent(device, 0, 7,
1, 0, 0);
xf86PostButtonEvent(device, 0, 7,
0, 0, 0);
movezx -= 1;
}
while (movezx <= -1) { /* left */
xf86PostButtonEvent(device, 0, 6,
1, 0, 0);
xf86PostButtonEvent(device, 0, 6,
0, 0, 0);
movezx += 1;
}
if ((priv->mouse_enabled == FALSE) &&
(priv->keys_enabled == FALSE))
nexttimer = 0;
if (nexttimer == 0) { /* No next timer (no subpixel added), stop */
priv->timerrunning = FALSE;
for (i=0; i<MAXBUTTONS; i++) priv->button[i].subpixel = 0.0f;
for (i=0; i<MAXAXES; i++) priv->axis[i].subpixel = 0.0f;
DBG(2, ErrorF("Stopping Axis Timer\n"));
}
xf86UnblockSIGIO (sigstate);
return nexttimer;
}
/***********************************************************************
*
* jstkStartAxisTimer --
*
* Starts the timer for the movement.
* Will already prepare for moving one pixel, for "tipping" the stick
*
***********************************************************************
*/
void
jstkStartAxisTimer(InputInfoPtr device, int number)
{
int pixel;
JoystickDevPtr priv = device->private;
if (priv->timerrunning) return;
priv->timerrunning = TRUE;
pixel = 1;
if (priv->axis[number].value < 0) pixel = -1;
priv->axis[number].subpixel += pixel;
DBG(2, ErrorF("Starting Axis Timer (triggered by axis %d)\n", number));
priv->timer = TimerSet(
priv->timer,
0, /* Relative */
1, /* What about NOW? */
jstkAxisTimer,
device->dev);
}
/***********************************************************************
*
* jstkStartButtonAxisTimer --
*
* Starts the timer for the movement.
* Will already prepare for moving one pixel, for "tipping" the stick
*
***********************************************************************
*/
void
jstkStartButtonAxisTimer(InputInfoPtr device, int number)
{
int pixel;
JoystickDevPtr priv = device->private;
if (priv->timerrunning) return;
priv->timerrunning = TRUE;
pixel = 1;
if (priv->button[number].amplify < 0) pixel = -1;
switch (priv->button[number].mapping) {
case JSTK_MAPPING_X:
case JSTK_MAPPING_Y:
case JSTK_MAPPING_ZX:
case JSTK_MAPPING_ZY:
priv->button[number].subpixel += pixel;
break;
default:
break;
}
DBG(2, ErrorF("Starting Axis Timer (triggered by button %d)\n", number));
priv->timer = TimerSet(
priv->timer,
0, /* Relative */
1, /* What about NOW? */
jstkAxisTimer,
device->dev);
}
/***********************************************************************
*
* jstkHandleAbsoluteAxis --
*
* Sums up absolute movement of all axes and sets the cursor to the
* desired Position on the screen.
*
***********************************************************************
*/
void
jstkHandleAbsoluteAxis(InputInfoPtr device, int number)
{
JoystickDevPtr priv = device->private;
int i,x,y;
x=0;
y=0;
for (i=0; i<MAXAXES; i++)
if (priv->axis[i].type == JSTK_TYPE_ABSOLUTE)
{
float rel;
int dif;
rel = 0.0f;
if (priv->axis[i].value > +priv->axis[i].deadzone)
rel = (priv->axis[i].value - priv->axis[i].deadzone);
if (priv->axis[i].value < -priv->axis[i].deadzone)
rel = (priv->axis[i].value + priv->axis[i].deadzone);
rel = (rel) / (2.0f * (float)(32768 - priv->axis[i].deadzone));
/* rel contains numbers between -0.5 and +0.5 now */
rel *= priv->axis[i].amplify;
DBG(5, ErrorF("Relative Position of axis %d: %.2f\n", i, rel));
/* Calculate difference to previous position on screen in pixels */
dif = (int)(rel - priv->axis[i].previousposition + 0.5f);
if ((dif >= 1)||(dif <= -1)) {
if (priv->axis[i].mapping == JSTK_MAPPING_X) {
x += (dif);
priv->axis[i].previousposition += (float)dif;
}
if (priv->axis[i].mapping == JSTK_MAPPING_Y) {
y += (int)(dif);
priv->axis[i].previousposition += (float)dif;
}
}
}
/* Still move relative, but relative to previous position of the axis */
if ((x != 0) || (y != 0)) {
DBG(4, ErrorF("Moving mouse by %dx%d pixels\n", x, y));
xf86PostMotionEvent(device->dev, 0, 0, 2, x, y);
}
}
/***********************************************************************
*
* jstkPWMAxisTimer --
*
* The timer that will generate Key events.
* The deflection of the axis will control the PERCENT OF TIME the key is
* down, not the amount of impulses.
* Return 0, when timer can be stopped.
*
***********************************************************************
*/
static CARD32
jstkPWMAxisTimer(OsTimerPtr timer,
CARD32 atime,
pointer arg)
{
DeviceIntPtr device = (DeviceIntPtr)arg;
InputInfoPtr pInfo = device->public.devicePrivate;
JoystickDevPtr priv = pInfo->private;
int sigstate, i;
int nexttimer;
nexttimer = 0;
sigstate = xf86BlockSIGIO();
for (i=0; i<MAXAXES; i++)
if (priv->axis[i].timer == timer) /* The timer handles only one axis! Find it. */
{
AXIS *axis;
axis = &priv->axis[i];
DBG(8, ErrorF("PWM Axis %d value %d (old %d)\n", i, axis->value, axis->oldvalue));
/* Force key_high down if centered */
if ((axis->value <= 0) &&
(axis->oldvalue > 0) &&
(axis->key_isdown))
{
DBG(7, ErrorF("PWM Axis %d jumped over. Forcing keys_high up.\n", i));
jstkGenerateKeys(priv->keyboard_device,
axis->keys_high,
0);
axis->key_isdown = 0;
}
/* Force key_low down if centered */
if ((axis->value >= 0) &&
(axis->oldvalue < 0) &&
(axis->key_isdown))
{
DBG(7, ErrorF("PWM Axis %d jumped over. Forcing keys_low up.\n", i));
jstkGenerateKeys(priv->keyboard_device,
axis->keys_low,
0);
axis->key_isdown = 0;
}
if (axis->value == 0)
nexttimer = 0;
else {
float time_on, time_off;
float scale;
KEYSCANCODES *keys;
if (axis->value < 0)
keys = &axis->keys_low;
else keys = &axis->keys_high;
/* Calculate next timer */
time_on = (float)(abs(axis->value) - axis->deadzone) / 32768.0;
time_on *= (32768.0f / (float)(32768 - axis->deadzone));
time_off = 1.0f - time_on;
time_on += 0.01f; /* Ugly but ensures we don't divide by 0 */
time_off += 0.01f;
/* Scale both durations, so the smaller always is 50ms */
scale = 50.0f * axis->amplify;
if (time_on < time_off)
scale /= time_on;
else scale /= time_off;
time_on *= scale;
time_off *= scale;
if (time_off > 600.0f) {
/* Might as well just have it down forever */
DBG(7, ErrorF("PWM Axis %d up time too long (%.0fms). Forcing up)\n", i, time_off));
if (axis->key_isdown == 1) {
axis->key_isdown = 0;
jstkGenerateKeys(priv->keyboard_device,
*keys,
axis->key_isdown);
}
nexttimer = 0;
} else if (time_on > 600.0f) {
/* Might as well just have it up forever */
DBG(7, ErrorF("PWM Axis %d down time too long (%.0fms). Forcing down)\n", i, time_on));
if (axis->key_isdown == 0) {
axis->key_isdown = 1;
jstkGenerateKeys(priv->keyboard_device,
*keys,
axis->key_isdown);
}
nexttimer = 0;
} else {
/* Flip key state */
axis->key_isdown = 1 - axis->key_isdown;
jstkGenerateKeys(priv->keyboard_device,
*keys,
axis->key_isdown);
DBG(7, ErrorF("PWM Axis %d state=%d (%.0fms down, %.0fms up).\n", i, axis->key_isdown, time_on, time_off));
nexttimer = axis->key_isdown ? (int)time_on : (int)time_off;
}
}
if (nexttimer == 0) { /* No next timer, stop */
axis->timerrunning = FALSE;
DBG(2, ErrorF("Stopping PWM Axis %d Timer\n", i));
}
axis->oldvalue = axis->value;
break;
}
xf86UnblockSIGIO (sigstate);
return nexttimer;
}
/***********************************************************************
*
* jstkStartAxisTimer --
*
* Starts the timer for the movement.
* Will already prepare for moving one pixel, for "tipping" the stick
*
***********************************************************************
*/
void
jstkHandlePWMAxis(InputInfoPtr device, int number)
{
JoystickDevPtr priv = device->private;
if (priv->axis[number].timerrunning) return;
priv->axis[number].timerrunning = TRUE;
DBG(2, ErrorF("Starting PWM Axis Timer (triggered by axis %d, value %d)\n",
number, priv->axis[number].value));
priv->axis[number].timer = TimerSet(
priv->axis[number].timer,
0, /* Relative */
1, /* What about NOW? */
jstkPWMAxisTimer,
device->dev);
}