/* * Copyright 2007-2008 by Sascha Hlusiak. * * 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 #include #include #include #include #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; iaxis[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 inbetween, * 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; ikeyboard_device, axis->keys_low, 1); jstkGenerateKeys(priv->keyboard_device, axis->keys_low, 0); } } else { for (i=0; ikeyboard_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; ibutton[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; ibutton[i].subpixel = 0.0f; for (i=0; iaxis[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; iaxis[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; iaxis[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); }