changeset 6192:71bc38e99d6d

RT-35540 [Monocle] Provide a touch event filter pipeline
author Daniel Blaukopf <daniel.blaukopf@oracle.com>
date Mon, 27 Jan 2014 05:08:56 +0200
parents 75acea53dd57
children a2712f27310e
files modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchInput.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchLookahead.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchState.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchStates.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/AssignPointIDTouchFilter.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/LookaheadTouchFilter.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/NearbyPointsTouchFilter.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/SmallMoveTouchFilter.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/TouchFilter.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/TouchPipeline.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDevice.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDeviceRegistry.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxSimpleTouchProcessor.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxStatefulMultiTouchProcessor.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxStatelessMultiTouchProcessor.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxTouchProcessor.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/MonocleUInput.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/NTrigTest.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/NativeUInput.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/TestApplication.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/TouchPipelineTest.java
diffstat 21 files changed, 1138 insertions(+), 396 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchInput.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchInput.java	Mon Jan 27 05:08:56 2014 +0200
@@ -31,6 +31,7 @@
 import com.sun.glass.ui.View;
 import com.sun.glass.ui.Window;
 import com.sun.glass.ui.monocle.MonocleWindow;
+import com.sun.glass.ui.monocle.input.filters.TouchPipeline;
 
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -42,15 +43,22 @@
 public class TouchInput {
 
     /**
-     * This property determine the sensitivity of move events from touch. The
+     * This property determines the sensitivity of move events from touch. The
      * bigger the value the less sensitive is the touch screen. In practice move
      * events with a delta smaller then the value of this property will be
      * filtered out.The value of the property is in pixels.
-     * Property is used by Lens native input driver
      */
-    private final int touchMoveSensitivity;
+    private final int touchRadius = AccessController.doPrivileged(
+            new PrivilegedAction<Integer>() {
+                @Override
+                public Integer run() {
+                    return Integer.getInteger(
+                            "monocle.input.touchRadius", 20);
+                }
+            });
 
     private static TouchInput instance = new TouchInput();
+    private TouchPipeline basePipeline;
 
     private TouchState state = new TouchState();
     private final GestureSupport gestures = new GestureSupport(false);
@@ -62,14 +70,28 @@
     }
 
     private TouchInput() {
-        touchMoveSensitivity = AccessController.doPrivileged(
-                new PrivilegedAction<Integer>() {
-                    @Override
-                    public Integer run() {
-                        return Integer.getInteger(
-                                "monocle.input.touch.MoveSensitivity", 20);
-                    }
-                });
+    }
+
+    /** Gets the base touch filter pipeline common to all touch devices */
+    public TouchPipeline getBasePipeline() {
+        if (basePipeline == null) {
+            basePipeline = new TouchPipeline();
+            String[] touchFilterNames = AccessController.doPrivileged(
+                    new PrivilegedAction<String>() {
+                        @Override
+                        public String run() {
+                            return System.getProperty(
+                                    "monocle.input.touchFilters",
+                                    "SmallMove");
+                        }
+                    }).split(",");
+            if (touchFilterNames != null) {
+                for (String touchFilterName : touchFilterNames) {
+                    basePipeline.addNamedFilter(touchFilterName.trim());
+                }
+            }
+        }
+        return basePipeline;
     }
 
     /** Copies the current state into the TouchState provided.
@@ -94,15 +116,9 @@
      * touch and mouse events.
      *
      * @param newState The updated touch state
-     * @param assignIDs true if the input processor didn't assign IDs to
-     *                  touch points; false if it did.
      */
-    public void setState(TouchState newState, boolean assignIDs) {
-        if (assignIDs) {
-            TouchStates.assignIDs(newState, state);
-        }
+    public void setState(TouchState newState) {
         newState.sortPointsByID();
-        TouchStates.filterSmallMoves(newState, state, touchMoveSensitivity);
         newState.assignPrimaryID();
         // Get the cached window for the old state and compute the window for
         // the new state
@@ -210,8 +226,8 @@
         return count;
     }
 
-    public int getTouchMoveSensitivity() {
-        return touchMoveSensitivity;
+    public int getTouchRadius() {
+        return touchRadius;
     }
 
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchLookahead.java	Sun Jan 26 23:49:36 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.glass.ui.monocle.input;
-
-/**
- * TouchLookahead handles compression of touch event streams by folding
- * together adjacent events that differ only in their coordinates but not in
- * number of touch points or the IDs assigned to those points.
- *
- * pullState(..) gets the current state from TouchInput
- * pushState() updates TouchInput with new touch point data
- * flushState() must be called at the end of event processing to clear the
- * pipeline.
- */
-public class TouchLookahead {
-
-    private TouchInput touch = TouchInput.getInstance();
-    private TouchState previousState = new TouchState();
-    private TouchState state = new TouchState();
-    private boolean assignIDs;
-    private boolean processedFirstEvent;
-
-    public TouchState getState() {
-        return state;
-    }
-
-    /** Sets whether or not we are asking TouchInput to assign touch point IDs */
-    public void setAssignIDs(boolean assignIDs) {
-        this.assignIDs = assignIDs;
-    }
-
-    /**
-     * Updates the local touch point state from TouchInput
-     *
-     * @param clearPoints Whether to clear touch point data in the updated local
-     *                    state. Stateless Touch processors getting their input
-     *                    with drivers that send each touch point on every event
-     *                    might need to set this; touch processors using drivers
-     *                    that send only the delta from the previous state will
-     *                    not want to clear the points.
-     */
-    public void pullState(boolean clearPoints) {
-        touch.getState(state);
-        if (clearPoints) {
-            state.clear();
-        }
-    }
-
-    public void pushState() {
-        if (!processedFirstEvent) {
-            touch.getState(previousState);
-            if (state.canBeFoldedWith(previousState, assignIDs)) {
-                processedFirstEvent = true;
-            } else {
-                touch.setState(state, assignIDs);
-            }
-        }
-        if (processedFirstEvent) {
-            // fold together TouchStates that have the same touch point count
-            // and IDs. For Protocol A devices the touch IDs are not initialized
-            // yet, which means the only differentiator will be the number of
-            // points.
-            state.sortPointsByID();
-            if (!state.canBeFoldedWith(previousState, assignIDs)) {
-                // the events are different. Send "previousState".
-                touch.setState(previousState, assignIDs);
-            }
-        }
-        state.copyTo(previousState);
-    }
-
-    public void flushState() {
-        touch.setState(previousState, assignIDs);
-        processedFirstEvent = false;
-    }
-
-}
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchState.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchState.java	Mon Jan 27 05:08:56 2014 +0200
@@ -186,7 +186,7 @@
         return sb.toString();
     }
 
-    void sortPointsByID() {
+    public void sortPointsByID() {
         Arrays.sort(points, 0, pointCount, pointIdComparator);
     }
 
@@ -214,7 +214,7 @@
      * @param ts the TouchState to compare to
      * @param ignoreIDs if true, ignore IDs when comparing points
      */
-    boolean canBeFoldedWith(TouchState ts, boolean ignoreIDs) {
+    public boolean canBeFoldedWith(TouchState ts, boolean ignoreIDs) {
         if (ts.pointCount != pointCount) {
             return false;
         }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/TouchStates.java	Sun Jan 26 23:49:36 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,169 +0,0 @@
-/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.sun.glass.ui.monocle.input;
-
-import com.sun.glass.ui.monocle.util.IntSet;
-
-import java.util.Arrays;
-
-public class TouchStates {
-
-    private static int[] mappedIndices = new int[1];
-    private static IntSet ids = new IntSet();
-    private static int nextID = 1;
-
-    /** Acquire a touch point ID */
-    private static int acquireID() {
-        ids.addInt(nextID);
-        return nextID ++;
-    }
-
-    /** Release a touch point ID */
-    private static void releaseID(int id) {
-        ids.removeInt(id);
-        nextID = 1;
-        for (int i = 0; i < ids.size(); i++) {
-            nextID = Math.max(ids.get(i) + 1, nextID);
-        }
-    }
-
-    /** Assign touch point IDs, for protocol A multitouch drivers that do not
-     * assign IDs themselves. */
-    static void assignIDs(TouchState state, TouchState oldState) {
-        if (oldState.getPointCount() == 0) {
-            for (int i = 0; i < state.getPointCount(); i++) {
-                state.getPoint(i).id = acquireID();
-            }
-        } else if (state.getPointCount() >= oldState.getPointCount()) {
-            // For each existing touch point, find the closest pending touch
-            // point.
-            // mappedIndices contains 0 for every unmapped pending touch point
-            // index  and 1 for every pending touch point index that has
-            // already been mapped to an existing touch point.
-            if (mappedIndices.length < state.getPointCount()) {
-                mappedIndices = new int[state.getPointCount()];
-            } else {
-                Arrays.fill(mappedIndices, 0);
-            }
-            int mappedIndexCount = 0;
-            for (int i = 0; i < oldState.getPointCount(); i++) {
-                TouchState.Point oldPoint = oldState.getPoint(i);
-                int x = oldPoint.x;
-                int y = oldPoint.y;
-                int closestDistanceSquared = Integer.MAX_VALUE;
-                int mappedIndex = -1;
-                for (int j = 0; j < state.getPointCount(); j++) {
-                    if (mappedIndices[j] == 0) {
-                        TouchState.Point newPoint = state.getPoint(j);
-                        int distanceX = x - newPoint.x;
-                        int distanceY = y - newPoint.y;
-                        int distanceSquared = distanceX * distanceX + distanceY * distanceY;
-                        if (distanceSquared < closestDistanceSquared) {
-                            mappedIndex = j;
-                            closestDistanceSquared = distanceSquared;
-                        }
-                    }
-                }
-                assert(mappedIndex >= 0);
-                state.getPoint(mappedIndex).id = oldPoint.id;
-                mappedIndexCount ++;
-                mappedIndices[mappedIndex] = 1;
-            }
-            if (mappedIndexCount < state.getPointCount()) {
-                for (int i = 0; i < state.getPointCount(); i++) {
-                    if (mappedIndices[i] == 0) {
-                        state.getPoint(i).id = acquireID();
-                    }
-                }
-            }
-        } else {
-            // There are more existing touch points than pending touch points.
-            // For each pending touch point, find the closest existing touch
-            // point.
-            // mappedIndices contains 0 for every unmapped pre-existing touch
-            // index and 1 for every pre-existing touch index that has already
-            // been mapped to a pending touch point
-            if (mappedIndices.length < oldState.getPointCount()) {
-                mappedIndices = new int[oldState.getPointCount()];
-            } else {
-                Arrays.fill(mappedIndices, 0);
-            }
-            int mappedIndexCount = 0;
-            for (int i = 0; i < state.getPointCount()
-                    && mappedIndexCount < oldState.getPointCount(); i++) {
-                TouchState.Point newPoint = state.getPoint(i);
-                int x = newPoint.x;
-                int y = newPoint.y;
-                int j;
-                int closestDistanceSquared = Integer.MAX_VALUE;
-                int mappedIndex = -1;
-                for (j = 0; j < oldState.getPointCount(); j++) {
-                    if (mappedIndices[j] == 0) {
-                        TouchState.Point oldPoint = oldState.getPoint(j);
-                        int distanceX = x - oldPoint.x;
-                        int distanceY = y - oldPoint.y;
-                        int distanceSquared = distanceX * distanceX + distanceY * distanceY;
-                        if (distanceSquared < closestDistanceSquared) {
-                            mappedIndex = j;
-                            closestDistanceSquared = distanceSquared;
-                        }
-                    }
-                }
-                assert(mappedIndex >= 0);
-                state.getPoint(i).id = oldState.getPoint(mappedIndex).id;
-                mappedIndexCount ++;
-                mappedIndices[mappedIndex] = 1;
-            }
-        }
-        // Release unused IDs. This can only be done after we have finished
-        // assigning all new IDs.
-        for (int i = 0; i < oldState.getPointCount(); i++) {
-            int id = oldState.getPoint(i).id;
-            TouchState.Point p = state.getPointForID(id, false);
-            if (p == null) {
-                releaseID(id);
-            }
-        }
-    }
-
-    static void filterSmallMoves(TouchState state, TouchState oldState, int radius) {
-        for (int i = 0; i < oldState.getPointCount(); i++) {
-            TouchState.Point oldPoint = oldState.getPoint(i);
-            TouchState.Point newPoint = state.getPointForID(oldPoint.id, false);
-            if (newPoint != null) {
-                int dx = newPoint.x - oldPoint.x;
-                int dy = newPoint.y - oldPoint.y;
-                int dist2 = dx * dx + dy * dy;
-                if (dist2 < radius * radius) {
-                    // if this is a move, rewrite it as a stationary event
-                    newPoint.x = oldPoint.x;
-                    newPoint.y = oldPoint.y;
-                }
-            }
-        }
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/AssignPointIDTouchFilter.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input.filters;
+
+import com.sun.glass.ui.monocle.input.TouchInput;
+import com.sun.glass.ui.monocle.input.TouchState;
+import com.sun.glass.ui.monocle.util.IntSet;
+
+import java.util.Arrays;
+
+public final class AssignPointIDTouchFilter implements TouchFilter {
+
+    private final TouchState oldState = new TouchState();
+    private int[] mappedIndices = new int[1];
+    private IntSet ids = new IntSet();
+    private int nextID = 1;
+
+    /** Acquire a touch point ID */
+    private int acquireID() {
+        ids.addInt(nextID);
+        return nextID ++;
+    }
+
+    /** Release a touch point ID */
+    private void releaseID(int id) {
+        ids.removeInt(id);
+        nextID = 1;
+        for (int i = 0; i < ids.size(); i++) {
+            nextID = Math.max(ids.get(i) + 1, nextID);
+        }
+    }
+
+    @Override
+    public int getPriority() {
+        return PRIORITY_ID;
+    }
+
+    /** Assign touch point IDs, for protocol A multitouch drivers that do not
+     * assign IDs themselves. */
+    @Override
+    public boolean filter(TouchState state) {
+        TouchInput.getInstance().getState(oldState);
+        if (oldState.getPointCount() == 0) {
+            for (int i = 0; i < state.getPointCount(); i++) {
+                state.getPoint(i).id = acquireID();
+            }
+        } else if (state.getPointCount() >= oldState.getPointCount()) {
+            // For each existing touch point, find the closest pending touch
+            // point.
+            // mappedIndices contains 0 for every unmapped pending touch point
+            // index  and 1 for every pending touch point index that has
+            // already been mapped to an existing touch point.
+            if (mappedIndices.length < state.getPointCount()) {
+                mappedIndices = new int[state.getPointCount()];
+            } else {
+                Arrays.fill(mappedIndices, 0);
+            }
+            int mappedIndexCount = 0;
+            for (int i = 0; i < oldState.getPointCount(); i++) {
+                TouchState.Point oldPoint = oldState.getPoint(i);
+                int x = oldPoint.x;
+                int y = oldPoint.y;
+                int closestDistanceSquared = Integer.MAX_VALUE;
+                int mappedIndex = -1;
+                for (int j = 0; j < state.getPointCount(); j++) {
+                    if (mappedIndices[j] == 0) {
+                        TouchState.Point newPoint = state.getPoint(j);
+                        int distanceX = x - newPoint.x;
+                        int distanceY = y - newPoint.y;
+                        int distanceSquared = distanceX * distanceX + distanceY * distanceY;
+                        if (distanceSquared < closestDistanceSquared) {
+                            mappedIndex = j;
+                            closestDistanceSquared = distanceSquared;
+                        }
+                    }
+                }
+                assert(mappedIndex >= 0);
+                state.getPoint(mappedIndex).id = oldPoint.id;
+                mappedIndexCount ++;
+                mappedIndices[mappedIndex] = 1;
+            }
+            if (mappedIndexCount < state.getPointCount()) {
+                for (int i = 0; i < state.getPointCount(); i++) {
+                    if (mappedIndices[i] == 0) {
+                        state.getPoint(i).id = acquireID();
+                    }
+                }
+            }
+        } else {
+            // There are more existing touch points than pending touch points.
+            // For each pending touch point, find the closest existing touch
+            // point.
+            // mappedIndices contains 0 for every unmapped pre-existing touch
+            // index and 1 for every pre-existing touch index that has already
+            // been mapped to a pending touch point
+            if (mappedIndices.length < oldState.getPointCount()) {
+                mappedIndices = new int[oldState.getPointCount()];
+            } else {
+                Arrays.fill(mappedIndices, 0);
+            }
+            int mappedIndexCount = 0;
+            for (int i = 0; i < state.getPointCount()
+                    && mappedIndexCount < oldState.getPointCount(); i++) {
+                TouchState.Point newPoint = state.getPoint(i);
+                int x = newPoint.x;
+                int y = newPoint.y;
+                int j;
+                int closestDistanceSquared = Integer.MAX_VALUE;
+                int mappedIndex = -1;
+                for (j = 0; j < oldState.getPointCount(); j++) {
+                    if (mappedIndices[j] == 0) {
+                        TouchState.Point oldPoint = oldState.getPoint(j);
+                        int distanceX = x - oldPoint.x;
+                        int distanceY = y - oldPoint.y;
+                        int distanceSquared = distanceX * distanceX + distanceY * distanceY;
+                        if (distanceSquared < closestDistanceSquared) {
+                            mappedIndex = j;
+                            closestDistanceSquared = distanceSquared;
+                        }
+                    }
+                }
+                assert(mappedIndex >= 0);
+                state.getPoint(i).id = oldState.getPoint(mappedIndex).id;
+                mappedIndexCount ++;
+                mappedIndices[mappedIndex] = 1;
+            }
+        }
+        // Release unused IDs. This can only be done after we have finished
+        // assigning all new IDs.
+        for (int i = 0; i < oldState.getPointCount(); i++) {
+            int id = oldState.getPoint(i).id;
+            TouchState.Point p = state.getPointForID(id, false);
+            if (p == null) {
+                releaseID(id);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean flush(TouchState state) {
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof AssignPointIDTouchFilter;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "AssignPointID";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/LookaheadTouchFilter.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input.filters;
+
+import com.sun.glass.ui.monocle.input.TouchInput;
+import com.sun.glass.ui.monocle.input.TouchState;
+
+public class LookaheadTouchFilter implements TouchFilter {
+
+    private TouchInput touch = TouchInput.getInstance();
+    private TouchState previousState = new TouchState();
+    private TouchState tmpState = new TouchState();
+    private boolean assignIDs;
+    private boolean processedFirstEvent;
+
+    /**
+     * Creates a new LookaheadTouchFilter
+     *
+     * @param assignIDs Sets whether or not we are asking the touch pipeline to
+     *                  assign touch point IDs
+     */
+    public LookaheadTouchFilter(boolean assignIDs) {
+        this.assignIDs = assignIDs;
+    }
+
+    @Override
+    public boolean filter(TouchState state) {
+        if (!processedFirstEvent) {
+            touch.getState(previousState);
+            if (state.canBeFoldedWith(previousState, assignIDs)) {
+                processedFirstEvent = true;
+            } else {
+                state.copyTo(previousState);
+                return false; // send state
+            }
+        }
+        if (processedFirstEvent) {
+            // fold together TouchStates that have the same touch point count
+            // and IDs. For Protocol A devices the touch IDs are not initialized
+            // yet, which means the only differentiator will be the number of
+            // points.
+            state.sortPointsByID();
+            if (!state.canBeFoldedWith(previousState, assignIDs)) {
+                // the events are different. Send "previousState".
+                state.copyTo(tmpState);
+                previousState.copyTo(state);
+                tmpState.copyTo(previousState);
+                return false;
+            }
+        }
+        state.copyTo(previousState);
+        return true;
+    }
+
+    @Override
+    public boolean flush(TouchState state) {
+        if (processedFirstEvent) {
+            previousState.copyTo(state);
+            processedFirstEvent = false;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int getPriority() {
+        return PRIORITY_PRE_ID + 1;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/NearbyPointsTouchFilter.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input.filters;
+
+import com.sun.glass.ui.monocle.input.TouchInput;
+import com.sun.glass.ui.monocle.input.TouchState;
+
+/**
+ * This TouchFilter merges together points that are closer together than a
+ * certain threshold.
+ */
+public final class NearbyPointsTouchFilter implements TouchFilter {
+
+    private final int radius = TouchInput.getInstance()
+            .getTouchRadius();
+
+    @Override
+    public int getPriority() {
+        return PRIORITY_PRE_ID;
+    }
+
+    @Override
+    public boolean filter(TouchState state) {
+        // keep merging points until the closest points are further part than
+        // radius
+        while (mergeClosestPoints(state)) {
+        }
+        return false;
+    }
+
+    private boolean mergeClosestPoints(TouchState state) {
+        int pointIndex1 = -1, pointIndex2 = -1;
+        int closestDistanceSquared = Integer.MAX_VALUE;
+        for (int i = 0; i < state.getPointCount(); i++) {
+            for (int j = i + 1; j < state.getPointCount(); j++) {
+                TouchState.Point p1 = state.getPoint(i);
+                TouchState.Point p2 = state.getPoint(j);
+                int dx = p1.x - p2.x;
+                int dy = p1.y - p2.y;
+                int distanceSquared = dx * dx + dy * dy;
+                if (distanceSquared < closestDistanceSquared) {
+                    closestDistanceSquared = distanceSquared;
+                    pointIndex1 = i;
+                    pointIndex2 = j;
+                }
+            }
+        }
+        if (closestDistanceSquared < radius * radius) {
+            TouchState.Point p1 = state.getPoint(pointIndex1);
+            TouchState.Point p2 = state.getPoint(pointIndex2);
+            p1.x = (p1.x + p2.x) / 2;
+            p1.y = (p1.y + p2.y) / 2;
+            state.removePointForID(p2.id);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public boolean flush(TouchState state) {
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof NearbyPointsTouchFilter;
+    }
+
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "NearbyPoints";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/SmallMoveTouchFilter.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input.filters;
+
+import com.sun.glass.ui.monocle.input.TouchInput;
+import com.sun.glass.ui.monocle.input.TouchState;
+
+/**
+ * This TouchFilter stabilizes touch points, eliminating moves below a certain
+ * threshold.
+ */
+public final class SmallMoveTouchFilter implements TouchFilter {
+
+    private final TouchState oldState = new TouchState();
+    private final int radius = TouchInput.getInstance()
+            .getTouchRadius();
+
+    @Override
+    public int getPriority() {
+        return PRIORITY_POST_ID;
+    }
+
+    @Override
+    public boolean filter(TouchState state) {
+        TouchInput.getInstance().getState(oldState);
+        for (int i = 0; i < oldState.getPointCount(); i++) {
+            TouchState.Point oldPoint = oldState.getPoint(i);
+            TouchState.Point newPoint = state.getPointForID(oldPoint.id, false);
+            if (newPoint != null) {
+                int dx = newPoint.x - oldPoint.x;
+                int dy = newPoint.y - oldPoint.y;
+                int dist2 = dx * dx + dy * dy;
+                if (dist2 < radius * radius) {
+                    // if this is a move, rewrite it as a stationary event
+                    newPoint.x = oldPoint.x;
+                    newPoint.y = oldPoint.y;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean flush(TouchState state) {
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof SmallMoveTouchFilter;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "SmallMove";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/TouchFilter.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input.filters;
+
+import com.sun.glass.ui.monocle.input.TouchState;
+
+/**
+ * A TouchFilter processes and modifies TouchStates before sending touch events
+ */
+public interface TouchFilter {
+
+    /**
+     * The priority value of a filter that is applied before IDs are assigned to
+     * points
+     */
+    public static final int PRIORITY_PRE_ID = 100;
+
+    /**
+     * The priority value of the filter that applies IDs to points
+     */
+    public static final int PRIORITY_ID = 0;
+
+    /**
+     * The priority value of a filter that is applied after IDs are assigned to
+     * points
+     */
+    public static final int PRIORITY_POST_ID = -100;
+
+    /**
+     * Filters a touch state
+     *
+     * @param state The new state to be filtered or modified
+     * @return true if the state is consumed, in which case no further
+     * processing will be done on this touch state and no events will be sent.
+     */
+    public boolean filter(TouchState state);
+
+    /**
+     * Flushes this filter's state. If this filter wants to send any additional
+     * events it should fill in the provided state object.
+     *
+     * @param state a TouchState object to be filled in by the filter
+     * @return true if the filter put data into the state
+     */
+    public boolean flush(TouchState state);
+
+    /**
+     * Gets the priority of this touch filter. Touch filters are applied in
+     * order, sorting first by their priority and then by the order in which
+     * they were requested. Higher priority numbers are applied first.
+     */
+    public int getPriority();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/filters/TouchPipeline.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input.filters;
+
+import com.sun.glass.ui.monocle.input.TouchInput;
+import com.sun.glass.ui.monocle.input.TouchState;
+
+import java.util.ArrayList;
+
+public class TouchPipeline {
+
+    private TouchInput touch = TouchInput.getInstance();
+    private ArrayList<TouchFilter> filters = new ArrayList<TouchFilter>();
+    private TouchState flushState = new TouchState();
+
+    /**
+     * Adds the filters in the given pipeline to this pipeline
+     */
+    public void add(TouchPipeline pipeline) {
+        for (int i = 0; i < pipeline.filters.size(); i++) {
+            addFilter(pipeline.filters.get(i));
+        }
+    }
+
+    /**
+     * Attempts to add the filters named in the comma-separated list provided.
+     */
+    public void addNamedFilters(String filterNameList) {
+        String[] touchFilterNames = filterNameList.split(",");
+        if (touchFilterNames != null) {
+            for (String touchFilterName : touchFilterNames) {
+                String s = touchFilterName.trim();
+                if (s.length() > 0) {
+                    addNamedFilter(s);
+                }
+            }
+        }
+    }
+
+    public void addNamedFilter(String filterName) {
+        try {
+            // install known filters without reflection
+            if (filterName.equals("SmallMove")) {
+                addFilter(new SmallMoveTouchFilter());
+            } else if (filterName.equals("NearbyPoints")) {
+                addFilter(new NearbyPointsTouchFilter());
+            } else if (filterName.equals("AssignPointID")) {
+                addFilter(new AssignPointIDTouchFilter());
+            } else {
+                Class cls;
+                if (!filterName.contains(".")) {
+                    filterName = "com.sun.glass.ui.monocle.input.filters."
+                            + filterName + "TouchFilter";
+                }
+                addFilter((TouchFilter) Class.forName(filterName).newInstance());
+            }
+        } catch (Exception e) {
+            System.err.println(
+                    "Cannot install touch filter '" + filterName + "'");
+            e.printStackTrace();
+        }
+    }
+
+    public void addFilter(TouchFilter filter) {
+        int priority = filter.getPriority();
+        int i;
+        for (i = 0; i < filters.size(); i++) {
+            if (filters.get(i).equals(filter)) {
+                return;
+            }
+            if (filters.get(i).getPriority() < priority) {
+                break;
+            }
+        }
+        filters.add(i, filter);
+    }
+
+    public boolean filter(TouchState state) {
+        for (int i = 0; i < filters.size(); i++) {
+            if (filters.get(i).filter(state)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void pushState(TouchState state) {
+        if (!filter(state)) {
+            touch.setState(state);
+        }
+    }
+
+    /** Flushes any remaining data in the pipeline, possibly pushing more
+     * state objects to TouchInput.
+     */
+    public void flush() {
+        for (int i = 0; i < filters.size(); i++) {
+            TouchFilter filter = filters.get(i);
+            while (filter.flush(flushState)) {
+                boolean consumed = false;
+                for (int j = i + 1; j < filters.size() && !consumed; j++) {
+                    consumed = filters.get(j).filter(flushState);
+                }
+                if (!consumed) {
+                    touch.setState(flushState);
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates the local touch point state from TouchInput
+     *
+     * @param clearPoints Whether to clear touch point data in the updated local
+     *                    state. Stateless Touch processors getting their input
+     *                    with drivers that send each touch point on every event
+     *                    might need to set this; touch processors using drivers
+     *                    that send only the delta from the previous state will
+     *                    not want to clear the points.
+     */
+    public void pullState(TouchState state, boolean clearPoints) {
+        touch.getState(state);
+        if (clearPoints) {
+            state.clear();
+        }
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer("TouchPipeline[");
+        for (int i = 0; i < filters.size(); i++) {
+            sb.append(filters.get(i));
+            if (i < filters.size() - 1) {
+                sb.append(" -> ");
+            }
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+}
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDevice.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDevice.java	Mon Jan 27 05:08:56 2014 +0200
@@ -29,6 +29,7 @@
 import com.sun.glass.ui.monocle.NativePlatformFactory;
 import com.sun.glass.ui.monocle.RunnableProcessor;
 import com.sun.glass.ui.monocle.input.InputDevice;
+import com.sun.glass.ui.monocle.input.filters.TouchPipeline;
 
 import java.io.File;
 import java.io.IOException;
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDeviceRegistry.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDeviceRegistry.java	Mon Jan 27 05:08:56 2014 +0200
@@ -131,7 +131,7 @@
                     return new LinuxStatelessMultiTouchProcessor(device);
                 }
             } else {
-                return new LinuxTouchProcessor(device);
+                return new LinuxSimpleTouchProcessor(device);
             }
         } else if (device.isRelative()) {
             return new LinuxMouseProcessor();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxSimpleTouchProcessor.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.linux;
+
+import com.sun.glass.ui.monocle.input.filters.AssignPointIDTouchFilter;
+import com.sun.glass.ui.monocle.input.filters.LookaheadTouchFilter;
+
+public class LinuxSimpleTouchProcessor extends LinuxTouchProcessor {
+
+    LinuxSimpleTouchProcessor(LinuxInputDevice device) {
+        super(device);
+        pipeline.addFilter(new LookaheadTouchFilter(true));
+        pipeline.addFilter(new AssignPointIDTouchFilter());
+    }
+
+    @Override
+    public void processEvents(LinuxInputDevice device) {
+        LinuxEventBuffer buffer = device.getBuffer();
+        pipeline.pullState(state, true);
+        boolean touchReleased = false;
+        while (buffer.hasNextEvent()) {
+            switch (buffer.getEventType()) {
+                case Input.EV_ABS: {
+                    int value = transform.getValue(buffer);
+                    switch (transform.getAxis(buffer)) {
+                        case Input.ABS_X:
+                        case Input.ABS_MT_POSITION_X:
+                            state.getPointForID(-1, true).x = value;
+                            break;
+                        case Input.ABS_Y:
+                        case Input.ABS_MT_POSITION_Y:
+                            state.getPointForID(-1, true).y = value;
+                            break;
+                    }
+                    break;
+                }
+                case Input.EV_KEY:
+                    switch (buffer.getEventCode()) {
+                        case Input.BTN_TOUCH:
+                            if (buffer.getEventValue() == 0) {
+                                touchReleased = true;
+                            } else {
+                                // restore an old point
+                                state.getPointForID(-1, true);
+                            }
+                            break;
+                    }
+                    break;
+                case Input.EV_SYN:
+                    switch (buffer.getEventCode()) {
+                        case Input.SYN_REPORT:
+                            if (touchReleased) {
+                                // remove points
+                                state.clear();
+                                touchReleased = false;
+                            }
+                            pipeline.pushState(state);
+                            pipeline.pullState(state, true);
+                            break;
+                        default: // ignore
+                    }
+                    break;
+            }
+            buffer.nextEvent();
+        }
+        pipeline.flush();
+    }
+
+}
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxStatefulMultiTouchProcessor.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxStatefulMultiTouchProcessor.java	Mon Jan 27 05:08:56 2014 +0200
@@ -25,8 +25,8 @@
 
 package com.sun.glass.ui.monocle.linux;
 
-import com.sun.glass.ui.monocle.input.TouchLookahead;
 import com.sun.glass.ui.monocle.input.TouchState;
+import com.sun.glass.ui.monocle.input.filters.LookaheadTouchFilter;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -34,26 +34,24 @@
 /**
  * This multitouch processor works with drivers that send tracking IDs.
  */
-public class LinuxStatefulMultiTouchProcessor implements LinuxInputProcessor {
+public class LinuxStatefulMultiTouchProcessor extends LinuxTouchProcessor {
 
     private static final int ID_UNASSIGNED = -1;
     private static final int SLOT_UNASSIGNED = -1;
     private static final int COORD_UNDEFINED = Integer.MIN_VALUE;
 
-    private final TouchLookahead tl = new TouchLookahead();
-    private final LinuxTouchTransform transform;
     private final Map<Integer, Integer> slotToIDMap =
             new HashMap<Integer, Integer>();
 
     LinuxStatefulMultiTouchProcessor(LinuxInputDevice device) {
-        tl.setAssignIDs(false);
-        transform = new LinuxTouchTransform(device);
+        super(device);
+        pipeline.addFilter(new LookaheadTouchFilter(false));
     }
 
     @Override
     public void processEvents(LinuxInputDevice device) {
         LinuxEventBuffer buffer = device.getBuffer();
-        tl.pullState(false);
+        pipeline.pullState(state, false);
         int currentID = ID_UNASSIGNED;
         int currentSlot = SLOT_UNASSIGNED;
         int x = COORD_UNDEFINED;
@@ -93,9 +91,9 @@
                         case Input.SYN_MT_REPORT: {
                             if (currentID != ID_UNASSIGNED) {
                                 if (x == COORD_UNDEFINED && y == COORD_UNDEFINED) {
-                                    tl.getState().removePointForID(currentID);
+                                    state.removePointForID(currentID);
                                 } else {
-                                    TouchState.Point p = tl.getState()
+                                    TouchState.Point p = state
                                             .getPointForID(currentID, false);
                                     if (p != null && p.id != currentID) {
                                         System.out.println("error");
@@ -103,7 +101,7 @@
                                     if (p == null) {
                                         p = new TouchState.Point();
                                         p.id = currentID;
-                                        p = tl.getState().addPoint(p);
+                                        p = state.addPoint(p);
                                     }
                                     if (x != COORD_UNDEFINED) {
                                         p.x = x;
@@ -118,8 +116,8 @@
                             break;
                         }
                         case Input.SYN_REPORT:
-                            tl.pushState();
-                            tl.pullState(false);
+                            pipeline.pushState(state);
+                            pipeline.pullState(state, false);
                             currentID = ID_UNASSIGNED;
                             currentSlot = SLOT_UNASSIGNED;
                             x = y = COORD_UNDEFINED;
@@ -130,7 +128,7 @@
             }
             buffer.nextEvent();
         }
-        tl.flushState();
+        pipeline.flush();
     }
 
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxStatelessMultiTouchProcessor.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxStatelessMultiTouchProcessor.java	Mon Jan 27 05:08:56 2014 +0200
@@ -25,8 +25,9 @@
 
 package com.sun.glass.ui.monocle.linux;
 
-import com.sun.glass.ui.monocle.input.TouchLookahead;
 import com.sun.glass.ui.monocle.input.TouchState;
+import com.sun.glass.ui.monocle.input.filters.AssignPointIDTouchFilter;
+import com.sun.glass.ui.monocle.input.filters.LookaheadTouchFilter;
 
 /**
  * This multitouch processor works with drivers that do not send a tracking ID
@@ -34,22 +35,20 @@
  * in each SYN_REPORT-terminated event sequence. So for these drivers we will
  * clear touch state before each event sequence.
  */
-public class LinuxStatelessMultiTouchProcessor implements LinuxInputProcessor {
+public class LinuxStatelessMultiTouchProcessor extends LinuxTouchProcessor {
 
     private static final int COORD_UNDEFINED = Integer.MIN_VALUE;
 
-    private final TouchLookahead tl = new TouchLookahead();
-    private final LinuxTouchTransform transform;
-
     LinuxStatelessMultiTouchProcessor(LinuxInputDevice device) {
-        tl.setAssignIDs(true);
-        transform = new LinuxTouchTransform(device);
+        super(device);
+        pipeline.addFilter(new LookaheadTouchFilter(true));
+        pipeline.addFilter(new AssignPointIDTouchFilter());
     }
 
     @Override
     public void processEvents(LinuxInputDevice device) {
         LinuxEventBuffer buffer = device.getBuffer();
-        tl.pullState(true);
+        pipeline.pullState(state, true);
         int x = COORD_UNDEFINED;
         int y = COORD_UNDEFINED;
         boolean touchReleased = false;
@@ -82,7 +81,7 @@
                     switch (buffer.getEventCode()) {
                         case Input.SYN_MT_REPORT: {
                             if (x != COORD_UNDEFINED && y != COORD_UNDEFINED) {
-                                TouchState.Point p = tl.getState().addPoint(null);
+                                TouchState.Point p = state.addPoint(null);
                                 p.id = 0;
                                 p.x = x;
                                 p.y = y;
@@ -93,10 +92,11 @@
                         case Input.SYN_REPORT:
                             if (touchReleased) {
                                 // remove points
-                                tl.getState().clear();
+                                state.clear();
                                 touchReleased = false;
-                            }                            tl.pushState();
-                            tl.pullState(true);
+                            }
+                            pipeline.pushState(state);
+                            pipeline.pullState(state, true);
                             x = y = COORD_UNDEFINED;
                             break;
                         default: // ignore
@@ -105,7 +105,7 @@
             }
             buffer.nextEvent();
         }
-        tl.flushState();
+        pipeline.flush();
     }
 
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxTouchProcessor.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxTouchProcessor.java	Mon Jan 27 05:08:56 2014 +0200
@@ -1,5 +1,5 @@
-/*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+package com.sun.glass.ui.monocle.linux;/*
+ * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,71 +23,35 @@
  * questions.
  */
 
-package com.sun.glass.ui.monocle.linux;
+import com.sun.glass.ui.monocle.input.TouchInput;
+import com.sun.glass.ui.monocle.input.TouchState;
+import com.sun.glass.ui.monocle.input.filters.TouchPipeline;
 
-import com.sun.glass.ui.monocle.input.TouchLookahead;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 
-public class LinuxTouchProcessor implements LinuxInputProcessor {
+public abstract class LinuxTouchProcessor implements LinuxInputProcessor {
 
-    private final TouchLookahead tl = new TouchLookahead();
-    private final LinuxTouchTransform transform;
+    protected final TouchState state = new TouchState();
+    protected final TouchPipeline pipeline;
+    protected final LinuxTouchTransform transform;
 
     LinuxTouchProcessor(LinuxInputDevice device) {
-        tl.setAssignIDs(true);
         transform = new LinuxTouchTransform(device);
-    }
-
-    @Override
-    public void processEvents(LinuxInputDevice device) {
-        LinuxEventBuffer buffer = device.getBuffer();
-        tl.pullState(true);
-        boolean touchReleased = false;
-        while (buffer.hasNextEvent()) {
-            switch (buffer.getEventType()) {
-                case Input.EV_ABS: {
-                    int value = transform.getValue(buffer);
-                    switch (transform.getAxis(buffer)) {
-                        case Input.ABS_X:
-                        case Input.ABS_MT_POSITION_X:
-                            tl.getState().getPointForID(-1, true).x = value;
-                            break;
-                        case Input.ABS_Y:
-                        case Input.ABS_MT_POSITION_Y:
-                            tl.getState().getPointForID(-1, true).y = value;
-                            break;
+        PrivilegedAction<String> getFilterProperty =
+                new PrivilegedAction<String>() {
+                    @Override
+                    public String run() {
+                        return System.getProperty(
+                                "monocle.input." + device.getProduct()
+                                        + ".touchFilters",
+                                "");
                     }
-                    break;
-                }
-                case Input.EV_KEY:
-                    switch (buffer.getEventCode()) {
-                        case Input.BTN_TOUCH:
-                            if (buffer.getEventValue() == 0) {
-                                touchReleased = true;
-                            } else {
-                                // restore an old point
-                                tl.getState().getPointForID(-1, true);
-                            }
-                            break;
-                    }
-                    break;
-                case Input.EV_SYN:
-                    switch (buffer.getEventCode()) {
-                        case Input.SYN_REPORT:
-                            if (touchReleased) {
-                                // remove points
-                                tl.getState().clear();
-                                touchReleased = false;
-                            }
-                            tl.pushState();
-                            tl.pullState(true);
-                            break;
-                        default: // ignore
-                    }
-                    break;
-            }
-            buffer.nextEvent();
-        }
-        tl.flushState();
+                };
+        pipeline = new TouchPipeline();
+        pipeline.addNamedFilters(
+                AccessController.doPrivileged(getFilterProperty));
+        pipeline.add(TouchInput.getInstance().getBasePipeline());
     }
 
 }
--- a/tests/system/src/test/java/com/sun/glass/ui/monocle/input/MonocleUInput.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/MonocleUInput.java	Mon Jan 27 05:08:56 2014 +0200
@@ -54,7 +54,13 @@
             pipe = Pipe.open();
         } catch (IOException e) {
             e.printStackTrace();
-        }        Application.invokeAndWait(() -> {
+        }
+        uevent.put("PRODUCT",
+                   Integer.toHexString(bus) + "/"
+                   + Integer.toHexString(vendor) + "/"
+                   + Integer.toHexString(product) + "/"
+                   + Integer.toHexString(version));
+        Application.invokeAndWait(() -> {
             device = registry.addDevice(
                     new LinuxInputDevice(capabilities,
                                          createAbsCapsMap(),
--- a/tests/system/src/test/java/com/sun/glass/ui/monocle/input/NTrigTest.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/NTrigTest.java	Mon Jan 27 05:08:56 2014 +0200
@@ -34,6 +34,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.CountDownLatch;
+
 /**
  * Test for events generated by the NTrig drivers
  */
@@ -419,8 +421,6 @@
             TestLog.waitForLogContaining("Mouse pressed", 3000);
             ui.processLine("EV_KEY BTN_TOUCH 0 ");
             ui.processLine("EV_SYN SYN_REPORT 0");
-            // avoid triggering the code for filtering out quick repeated taps
-            Thread.sleep(100);
         }
         TestRunnable.invokeAndWaitUntilSuccess(() -> {
             Assert.assertEquals(20, TestLog.countLogContaining("TouchPoint: PRESSED"));
@@ -434,15 +434,18 @@
     /** 20 quick taps while the application thread is busy */
     @Test
     public void tapTwentyTimesUnderStress() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
         // throttle the application thread
         final AnimationTimer a = new AnimationTimer() {
             @Override
             public void handle(long now) {
                 long end = now + 50000000; // 50 ms
+                latch.countDown();
                 while (System.nanoTime() < end) { } // spin
             }
         };
         Platform.runLater(() -> { a.start(); });
+        latch.await();
         try {
             for (int i = 0; i < 20; i++) {
                 ui.processLine("EV_ABS ABS_X 2048");
@@ -458,8 +461,6 @@
                 TestLog.waitForLogContaining("Mouse pressed", 3000);
                 ui.processLine("EV_KEY BTN_TOUCH 0 ");
                 ui.processLine("EV_SYN SYN_REPORT 0");
-                // avoid triggering the code for filtering out quick repeated taps
-                Thread.sleep(100);
             }
             TestRunnable.invokeAndWaitUntilSuccess(() -> {
                 Assert.assertEquals(20, TestLog.countLogContaining("TouchPoint: PRESSED"));
--- a/tests/system/src/test/java/com/sun/glass/ui/monocle/input/NativeUInput.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/NativeUInput.java	Mon Jan 27 05:08:56 2014 +0200
@@ -55,6 +55,7 @@
     protected final Map<String, String> udevManifest;
     protected final Map<String, String> uevent;
     protected final ByteBuffer event;
+    protected int bus, vendor, product, version;
 
     public NativeUInput() {
         event = ByteBuffer.allocateDirect(EVENT_STRUCT_SIZE);
@@ -210,11 +211,11 @@
             case "RELBIT": addCapability("rel", codeString); break;
             case "SNDBIT": addCapability("snd", codeString); break;
             case "SWBIT": addCapability("sw", codeString); break;
-            case "PROPBIT":
-            case "VENDOR":
-            case "PRODUCT":
-            case "VERSION":
-                break; // ignore
+            case "PROPBIT": break;
+            case "BUS": bus = valueToInt(codeString); break;
+            case "VENDOR": vendor = valueToInt(codeString); break;
+            case "PRODUCT": product = valueToInt(codeString); break;
+            case "VERSION": version = valueToInt(codeString); break;
             default: processLine3(line, typeString, codeString, "0");
         }
     }
--- a/tests/system/src/test/java/com/sun/glass/ui/monocle/input/TestApplication.java	Sun Jan 26 23:49:36 2014 +0200
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/TestApplication.java	Mon Jan 27 05:08:56 2014 +0200
@@ -43,6 +43,7 @@
 import org.junit.Assert;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -100,7 +101,7 @@
             ready.acquire();
             Platform.runLater(() -> {
                 if (isMonocle()) {
-                    tapRadius = TouchInput.getInstance().getTouchMoveSensitivity();
+                    tapRadius = TouchInput.getInstance().getTouchRadius();
                     useMultitouch = true;
                 } else {
                     tapRadius = Integer.getInteger("lens.input.touch.TapRadius", 20);
@@ -122,6 +123,7 @@
 
     public static void showFullScreenScene() throws Exception {
         TestApplication.getStage();
+        frameWait(2);
         new TestRunnable() {
             @Override
             public void test() throws Exception {
@@ -138,11 +140,13 @@
                 stage.requestFocus();
             }
         }.invokeAndWait();
-        waitForNextPulse();
+        frameWait(1);
     }
 
     public static void showInMiddleOfScreen() throws Exception {
         TestApplication.getStage();
+        // wait for events to finish being delivered to the previous scene
+        frameWait(2);
         new TestRunnable() {
             @Override
             public void test() throws Exception {
@@ -163,20 +167,29 @@
                 stage.requestFocus();
             }
         }.invokeAndWait();
-        waitForNextPulse();
+        frameWait(1);
     }
 
     public static void waitForNextPulse() throws InterruptedException {
-        final Semaphore done = new Semaphore(1);
-        done.acquire();
+        frameWait(1);
+    }
+
+    private static void frameWait(int n) {
+        final CountDownLatch frameCounter = new CountDownLatch(n);
         Platform.runLater(() -> new AnimationTimer() {
             @Override
             public void handle(long now) {
-                done.release();
-                stop();
+                frameCounter.countDown();
+                if (frameCounter.getCount() == 0l) {
+                    stop();
+                }
             }
         }.start());
-        done.acquire();
+        try {
+            frameCounter.await();
+        } catch (InterruptedException ex) {
+            Assert.fail("Unexpected exception: " + ex);
+        }
     }
 
     public static void addKeyListeners() throws Exception {
@@ -308,6 +321,7 @@
                     robot.destroy();
                 }
             });
+            frameWait(1);
         } finally {
             getStage().removeEventHandler(TouchEvent.TOUCH_RELEASED, touchHandler);
             ui.processLine("DESTROY");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/TouchPipelineTest.java	Mon Jan 27 05:08:56 2014 +0200
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.input;
+
+import com.sun.glass.ui.monocle.input.filters.TouchFilter;
+import javafx.geometry.Rectangle2D;
+import javafx.stage.Screen;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test installation of custom touch filters
+ */
+public class TouchPipelineTest extends TouchTestBase {
+
+    public static class TranslateFilter implements TouchFilter {
+        @Override
+        public boolean filter(TouchState state) {
+            for (int i = 0; i < state.getPointCount(); i++) {
+                state.getPoint(i).x += 8;
+                state.getPoint(i).y -= 5;
+            }
+            return false;
+        }
+
+        @Override
+        public int getPriority() {
+            return 50;
+        }
+
+        @Override
+        public boolean flush(TouchState state) {
+            return false;
+        }
+    }
+
+    public static class OverrideIDFilter implements TouchFilter {
+        @Override
+        public boolean filter(TouchState state) {
+            for (int i = 0; i < state.getPointCount(); i++) {
+                state.getPoint(i).id = 5;
+            }
+            return false;
+        }
+
+        @Override
+        public int getPriority() {
+            return -50;
+        }
+
+        @Override
+        public boolean flush(TouchState state) {
+            return false;
+        }
+    }
+
+    public static class NoMultiplesOfTenOnXFilter implements TouchFilter {
+        @Override
+        public boolean filter(TouchState state) {
+            for (int i = 0; i < state.getPointCount(); i++) {
+                if (state.getPoint(i).x % 10 == 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public int getPriority() {
+            return 60;
+        }
+
+        @Override
+        public boolean flush(TouchState state) {
+            return false;
+        }
+    }
+
+    public static class LoggingFilter implements TouchFilter {
+        @Override
+        public boolean filter(TouchState state) {
+            for (int i = 0; i < state.getPointCount(); i++) {
+                TestLog.format("Touch point id=%d at %d,%d",
+                               state.getPoint(i).id,
+                               state.getPoint(i).x,
+                               state.getPoint(i).y);
+            }
+            return false;
+        }
+
+        @Override
+        public int getPriority() {
+            return -100;
+        }
+
+        @Override
+        public boolean flush(TouchState state) {
+            return false;
+        }
+    }
+
+    public static class FlushingFilter implements TouchFilter {
+        int i;
+        @Override
+        public boolean filter(TouchState state) {
+            i = 3;
+            return false;
+        }
+
+        @Override
+        public int getPriority() {
+            return 90;
+        }
+
+        @Override
+        public boolean flush(TouchState state) {
+            if (i > 0) {
+                i --;
+                state.clear();
+                TouchState.Point p = state.addPoint(null);
+                p.x = 205 + i * 100;
+                p.y = 100;
+                p.id = -1;
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    @Before
+    public void createDevice() throws Exception {
+        Assume.assumeTrue(TestApplication.isMonocle());
+        ui = new UInput();
+        TestApplication.showFullScreenScene();
+        TestApplication.addMouseListeners();
+        TestApplication.addTouchListeners();
+        TestLog.reset();
+        System.setProperty("monocle.input.ca/fe/ba/be.touchFilters",
+                           TranslateFilter.class.getName() + ","
+                           + OverrideIDFilter.class.getName() +","
+                           + FlushingFilter.class.getName() +","
+                           + LoggingFilter.class.getName() +","
+                           + NoMultiplesOfTenOnXFilter.class.getName());
+        Rectangle2D r = Screen.getPrimary().getBounds();
+        ui.processLine("OPEN");
+        ui.processLine("EVBIT EV_SYN");
+        ui.processLine("EVBIT EV_KEY");
+        ui.processLine("KEYBIT BTN_TOUCH");
+        ui.processLine("EVBIT EV_ABS");
+        ui.processLine("ABSBIT ABS_X");
+        ui.processLine("ABSBIT ABS_Y");
+        ui.processLine("ABSMIN ABS_X 0");
+        ui.processLine("ABSMAX ABS_X " + (int) (r.getWidth() - 1));
+        ui.processLine("ABSMIN ABS_Y 0");
+        ui.processLine("ABSMAX ABS_Y " + (int) (r.getHeight() - 1));
+        ui.processLine("PROPBIT INPUT_PROP_POINTER");
+        ui.processLine("PROPBIT INPUT_PROP_DIRECT");
+        ui.processLine("PROPERTY ID_INPUT_TOUCHSCREEN 1");
+        ui.processLine("BUS 0xCA");
+        ui.processLine("VENDOR 0xFE");
+        ui.processLine("PRODUCT 0xBA");
+        ui.processLine("VERSION 0xBE");
+        ui.processLine("CREATE");
+    }
+
+
+    @Test
+    public void testFilters() throws Exception {
+        ui.processLine("EV_ABS ABS_X 195");
+        ui.processLine("EV_ABS ABS_Y 200");
+        ui.processLine("EV_SYN");
+        ui.processLine("EV_ABS ABS_X 200");
+        ui.processLine("EV_ABS ABS_Y 200");
+        ui.processLine("EV_SYN");
+        TestLog.waitForLog("Touch pressed: 203, 195");
+        TestLog.waitForLog("Touch point id=5 at 203,195");
+        // Check for events send by the flushing filter
+        TestLog.waitForLog("Touch moved: 413, 95");
+        TestLog.waitForLog("Touch moved: 313, 95");
+        TestLog.waitForLog("Touch moved: 213, 95");
+        // This one should have been filtered out
+        Assert.assertEquals(0, TestLog.countLog("Touch Pressed: 208, 195"));
+    }
+
+}
+