Here's a little experiment I threw together. First of all, sorry about the huge quantity of babbling. In short, you can jump to the bottom, apply the patch and say what you think.
In long (with chapters :P): The background (rationale?): The mouse is a successful input technique because the physical device makes sense. It "feels" like the mouse pointer on the screen and obeys the laws of physics if someone throws it (not that anyone would do that). The important part is that the user controls the pointer directly and those motions translate accurately on screen. In the real world, the interaction here would be understandable with any similarly sized object floating through whatever surface the mouse is being used on. The touchpad (trackpad) is often accused of being a difficult input device with severe ergonomics issues. With a touchpad, everything is an indirect type of motion without any physical feedback (which is perhaps why it has been, until now, widely despised). The touchpad doesn't move; it just acts as a window through which the pointer is nudged. At the moment the pointer is a difficult object to nudge. A bit like pushing it through syrupy goo. It just doesn't move unless the user PUSHES it at all times. It doesn't make as much sense and it doesn't feel like it applies to the real world, no matter what paper-like texture the manufacturer puts over the touchpad. (They should really be using a sticky goo texture if they want it to feel consistent). I believe this can all be fixed in software, so I tried! My idea: My idea is to give the pointer a more believable, and at the same time more comfortable presence when using the touchpad. Since the device itself doesn't provide any kind of feedback, we have to assume "the pointer" is fairly light, with a small amount of friction so it can be pushed easily. This way the user can interact more naturally. Silly, but I'm sure this can all be linked to some pseudo-scientific psychology babble. It's actually a really simple hack, too. If you haven't guessed it, this is all achieved through ridiculously primitive physics; essentially that kinetic scrolling stuff everyone loves. If the finger is lifted while moving (a flicking motion), the pointer keeps moving along its path, gradually slowed by friction. Normal pointer movement still works fine since people generally stop at the end of a movement while still touching the pad, and if not are likely to click it again in a moment. Correct me if wrong, but I have seen a few instances (counting myself :P) where people unconsciously flick the touchpad and expect the pointer to be flicked... but it is not! The patch: I attached a patch. My patch is unobtrusive, mainly adding code to synaptics.c (as well as some little configuration variables elsewhere). The heavy calculations are only carried out if PointerGlide is set to 1 (enabled). By default, PointerGlide is set to 0. Maths are proof of concept stage; not optimized. (Matt Helsley on the xorg list gave me some great suggestions to make those faster). synclient has been adjusted, adding support for the new configuration variables PointerGlide, PointerGlideFriction and PointerGlideMaxSpeed. To test, you will need to apply the (attached) patch to xf86-input-synaptics, then build both the patched driver and synclient. The patch is based on the driver from xorg's Git repository on freedesktop.org, but it should work in the apt-source copy as well. (Easy enough to do it manually, too; it isn't much code). The configuration can be done via xorg.conf, but synclient makes it much more pleasant. To enable this, run "synclient PointerGlide=1". The default friction and max speed is the same as right now on my computer (biggish touchpad, 14" screen with 1280x800 resolution). The other PointerGlide options are listed if you run synclient -l and can be played with at any time. The rest: I originally submitted this as an upstream feature request + patch, but the folks there convinced me that it was a bit too crazy (and untested) of an idea for them, though it may make sense somewhere closer to a desktop... So I am posting here to explore the idea. Two things: * What do you think? (Should I just accept that this is a ridiculous concept and move on, or could it be useful? :P). Is it comfortable? Confusing? Handy? Did you need to play with the settings? * Any suggestions for how this can be done outside of the driver? It creeped me out while working on this that my insignificant modifications to a little input driver could bring down the entire session in the event of a crash. I imagine it's a major pain to maintain... so are there examples of this type of stuff being implemented at a higher level? Personally, I think this does awesome things for ergonomics and probably is saving me from developing RSI when I am without a mouse I have asked a few other people to play with it, bringing positive results as well. However, I'd love to hear from anyone else on the topic :) Bye, Dylan
=== modified file 'include/synaptics-properties.h' --- include/synaptics-properties.h 2008-12-12 03:19:54 +0000 +++ include/synaptics-properties.h 2008-12-14 21:27:34 +0000 @@ -122,6 +122,15 @@ #define SYNAPTICS_PROP_CIRCULAR_PAD "Synaptics Circular Pad" /* 8 bit (BOOL) */ +#define SYNAPTICS_PROP_POINTER_GLIDE "Synaptics Pointer Glide" + +/* XXX: 32 bit */ +#define SYNAPTICS_PROP_POINTER_GLIDE_FRICTION "Synaptics Pointer Glide Friction" + +/* XXX: 32 bit */ +#define SYNAPTICS_PROP_POINTER_GLIDE_MAX_SPEED "Synaptics Pointer Glide Max Speed" + +/* 8 bit (BOOL) */ #define SYNAPTICS_PROP_PALM_DETECT "Synaptics Palm Detection" /* 32 bit, 2 values, width, z */ === modified file 'include/synaptics.h' --- include/synaptics.h 2008-12-12 03:19:54 +0000 +++ include/synaptics.h 2008-12-14 21:33:12 +0000 @@ -124,6 +124,9 @@ double scroll_dist_circ; /* Scrolling angle radians */ int circular_trigger; /* Trigger area for circular scrolling */ Bool circular_pad; /* Edge has an oval or circular shape */ + Bool pointer_glide; /* If true, pointer will continue moving for a moment after finger is lifted */ + double pointer_glide_friction; /* Friction coefficient used to slow down pointer glide. Defaults to 0.004 in synaptics.c */ + double pointer_glide_max_speed; /* Pointer glide is limited to move at this speed (in pixels / millisecond) */ Bool palm_detect; /* Enable Palm Detection */ int palm_min_width; /* Palm detection width */ int palm_min_z; /* Palm detection depth */ === modified file 'src/properties.c' --- src/properties.c 2008-12-12 03:19:54 +0000 +++ src/properties.c 2008-12-14 21:26:20 +0000 @@ -66,6 +66,9 @@ Atom prop_circscroll_dist = 0; Atom prop_circscroll_trigger = 0; Atom prop_circpad = 0; +Atom prop_pointer_glide = 0; +Atom prop_pointer_glide_friction = 0; +Atom prop_pointer_glide_max_speed = 0; Atom prop_palm = 0; Atom prop_palm_dim = 0; Atom prop_coastspeed = 0; @@ -188,6 +191,9 @@ /* FIXME: missing: scroll_dist_circ is a float */ prop_circscroll_trigger = InitAtom(local->dev, SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER, 8, 1, ¶->circular_trigger); prop_circpad = InitAtom(local->dev, SYNAPTICS_PROP_CIRCULAR_PAD, 8, 1, ¶->circular_pad); + prop_pointer_glide = InitAtom(local->dev, SYNAPTICS_PROP_POINTER_GLIDE, 8, 1, ¶->pointer_glide); + /* FIXME: missing: pointer_glide_friction is a float */ + /* FIXME: missing: prop_pointer_glide_max_speed is a float */ prop_palm = InitAtom(local->dev, SYNAPTICS_PROP_PALM_DETECT, 8, 1, ¶->palm_detect); values[0] = para->palm_min_width; @@ -465,6 +471,18 @@ return BadMatch; para->circular_pad = *(BOOL*)prop->data; + } else if (property == prop_pointer_glide) + { + if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) + return BadMatch; + + para->pointer_glide = *(BOOL*)prop->data; + } else if (property == prop_pointer_glide_friction) + { + /* FIXME */ + } else if (property == prop_pointer_glide_max_speed) + { + /* FIXME */ } else if (property == prop_palm) { if (prop->size != 1 || prop->format != 8 || prop->type != XA_INTEGER) === modified file 'src/synaptics.c' --- src/synaptics.c 2008-12-12 03:19:54 +0000 +++ src/synaptics.c 2009-01-12 22:45:01 +0000 @@ -489,7 +489,10 @@ pars->single_tap_timeout = xf86SetIntOption(opts, "SingleTapTimeout", 180); pars->press_motion_min_z = xf86SetIntOption(opts, "PressureMotionMinZ", pars->edge_motion_min_z); pars->press_motion_max_z = xf86SetIntOption(opts, "PressureMotionMaxZ", pars->edge_motion_max_z); - + pars->pointer_glide = xf86SetBoolOption(opts, "PointerGlide", FALSE); + + pars->pointer_glide_friction = synSetFloatOption(opts, "PointerGlideFriction", 0.006); + pars->pointer_glide_max_speed = synSetFloatOption(opts, "PointerGlideMaxSpeed", 5); /* Totally arbitrary default. 1.6 pixels / millisecond, 1600 pixels / second */ pars->min_speed = synSetFloatOption(opts, "MinSpeed", minSpeed); pars->max_speed = synSetFloatOption(opts, "MaxSpeed", maxSpeed); pars->accl = synSetFloatOption(opts, "AccelFactor", 0.0015); @@ -2055,10 +2058,58 @@ delay = MIN(delay, repeat_delay); } } + + if ( para->pointer_glide == TRUE && finger == FS_UNTOUCHED ) //Pointer glide stuff. If there is not a finger on the touchpad... + { + if ( priv->finger_state == FS_TOUCHED ) //...but there was previously + { + //Finger has just been lifted! Determine the velocity from before finger was lifted + int dtime = HIST(1).millis - HIST(2).millis; // Time in milliseconds between two recent direct pointer moves... + priv->glide_angle = atan2((double) priv->prev_dy,(double) priv->prev_dx); + priv->glide_speed = move_distance(priv->prev_dx,priv->prev_dy) / (double) dtime; /* Glide speed is pixels / millisecond */ + priv->glide_timestamp_millis = HIST(1).millis; + } + if ( priv->glide_speed != 0) //...and if we have built momentum for gliding + { + /* FIXME: I wish we could base this on para->max_speed, but that is based on pixels / packets */ + if (para->pointer_glide_max_speed != 0 && priv->glide_speed > para->pointer_glide_max_speed) + priv->glide_speed = para->pointer_glide_max_speed; + + int dtime = hw->millis - priv->glide_timestamp_millis; /* Time in milliseconds between last time glide function was performed and now */ + + double velocity_change = (double) dtime * para->pointer_glide_friction; + + /* Slow down gliding at a consistent rate... */ + if ( priv->glide_speed - velocity_change >= 0 ) + { + priv->glide_speed -= velocity_change; + } else if ( priv->glide_speed + velocity_change <= 0 ) + { + priv->glide_speed += velocity_change; + } else { + priv->glide_speed = 0; + } + + double glide = priv->glide_speed * dtime; + int glide_x = (int) (glide*cos(priv->glide_angle)); + int glide_y = (int) (glide*sin(priv->glide_angle)); + + xf86PostMotionEvent(local->dev, 0, 0, 2, glide_x, glide_y); + + priv->glide_timestamp_millis = hw->millis; + + delay = MIN(delay,10); + } + } else if ( priv->glide_speed != 0 ) + { + priv->glide_speed = 0; + } /* Save old values of some state variables */ priv->finger_state = finger; priv->lastButtons = buttons; + priv->prev_dx = dx; + priv->prev_dy = dy; return delay; } === modified file 'src/synapticsstr.h' --- src/synapticsstr.h 2008-12-12 03:19:54 +0000 +++ src/synapticsstr.h 2008-12-14 21:06:19 +0000 @@ -138,6 +138,12 @@ palm/finger contact disappears */ int prev_z; /* previous z value, for palm detection */ int avg_width; /* weighted average of previous fingerWidth values */ + + int prev_dx, prev_dy; /* Previous X and Y deltas for pointer movement */ + + int glide_timestamp_millis; /* Last time pointer glide was processed */ + double glide_angle; /* Angle of pointer glide */ + double glide_speed; /* Speed of pointer glide in pixels / millisecond */ int minx, maxx, miny, maxy; /* min/max dimensions as detected */ } SynapticsPrivate; === modified file 'tools/synclient.c' --- tools/synclient.c 2008-12-12 03:19:54 +0000 +++ tools/synclient.c 2009-01-12 22:41:12 +0000 @@ -114,6 +114,9 @@ DEFINE_PAR("CircScrollDelta", scroll_dist_circ, PT_DOUBLE, .01, 3), DEFINE_PAR("CircScrollTrigger", circular_trigger, PT_INT, 0, 8), DEFINE_PAR("CircularPad", circular_pad, PT_BOOL, 0, 1), + DEFINE_PAR("PointerGlide", pointer_glide, PT_BOOL, 0, 1), + DEFINE_PAR("PointerGlideFriction", pointer_glide_friction, PT_DOUBLE, 0, 1.0), + DEFINE_PAR("PointerGlideMaxSpeed", pointer_glide_max_speed, PT_DOUBLE, 0, 200.0), DEFINE_PAR("PalmDetect", palm_detect, PT_BOOL, 0, 1), DEFINE_PAR("PalmMinWidth", palm_min_width, PT_INT, 0, 15), DEFINE_PAR("PalmMinZ", palm_min_z, PT_INT, 0, 255),
signature.asc
Description: This is a digitally signed message part
-- Ubuntu-devel-discuss mailing list Ubuntu-devel-discuss@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/ubuntu-devel-discuss