changeset 6154:c0c2b5d913ec

RT-35391 [Monocle] Use hardware double-buffering in software rendering RT-35354 [Monocle] Provide robot capture interface in NativeScreen
author Daniel Blaukopf <daniel.blaukopf@oracle.com>
date Tue, 21 Jan 2014 02:49:34 +0200
parents 2abb15915443
children 96ad554b62b8
files modules/graphics/src/main/java/com/sun/glass/ui/monocle/Framebuffer.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleRobot.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleView.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleWindow.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleWindowManager.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/NativePlatform.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/NativeScreen.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/RunnableProcessor.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/RunnableQueue.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/headless/HeadlessScreen.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/KeyInput.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/MouseInput.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/FBDevScreen.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/GetEvent.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxFrameBuffer.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/LinuxInputProcessor.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxSystem.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/Udev.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/UdevListener.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/omap/OMAPScreen.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/util/C.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/x11/X11InputDeviceRegistry.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/x11/X11Screen.java modules/graphics/src/main/native-glass/monocle/linux/LinuxSystem.c modules/graphics/src/main/native-glass/monocle/util/C.c tests/system/src/test/java/com/sun/glass/ui/monocle/headless/HeadlessGeometry1Test.java tests/system/src/test/java/com/sun/glass/ui/monocle/headless/HeadlessGeometry2Test.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/EGalaxTest.java tests/system/src/test/java/com/sun/glass/ui/monocle/input/TestLog.java
diffstat 31 files changed, 1400 insertions(+), 331 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/Framebuffer.java	Tue Jan 21 02:49:34 2014 +0200
@@ -0,0 +1,264 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.nio.channels.FileChannel;
+
+public class Framebuffer {
+
+    private ByteBuffer bb;
+    private int width;
+    private int height;
+    private int byteDepth;
+    private boolean receivedData;
+    private ByteBuffer clearBuffer;
+    private ByteBuffer lineByteBuffer;
+    private Buffer linePixelBuffer;
+    private int address;
+
+    public Framebuffer(ByteBuffer bb, int width, int height, int depth, boolean clear) {
+        this.bb = bb;
+        this.width = width;
+        this.height = height;
+        this.byteDepth = depth >>> 3;
+        if (clear) {
+            clearBuffer = ByteBuffer.allocate(width * 4);
+        }
+    }
+
+    public ByteBuffer getBuffer() {
+        return bb;
+    }
+
+    public void reset() {
+        receivedData = false;
+    }
+
+    public void setStartAddress(int address) {
+        this.address = address;
+    }
+
+    public void clearBufferContents() {
+        bb.clear();
+        bb.position(address);
+        bb.limit(address + width * height * byteDepth);
+        for (int i = 0; i < height; i++) {
+            clearBuffer.clear();
+            bb.put(clearBuffer);
+        }
+    }
+
+    public boolean hasReceivedData() {
+        return receivedData;
+    }
+
+    public void composePixels(Buffer src,
+                              int pX, int pY, int pW, int pH,
+                              float alpha) {
+        int stride = pW * 4;
+        int start = 0;
+        if (pX < 0) {
+            start -= pX * 4;
+            pW += pX;
+            pX = 0;
+        }
+        if (pY < 0) {
+            start -= pY * stride;
+            pH += pY;
+            pY = 0;
+        }
+        if (pX + pW > width) {
+            pW = width - pX;
+        }
+        if (pY + pH > height) {
+            pH = height - pY;
+        }
+        int alphaMultiplier = Math.round(Math.min(alpha, 1f) * 256f);
+        if (pW < 0 || pH < 0 || alphaMultiplier <= 0) {
+            return;
+        }
+        // If clearBuffer is set, clear the buffer on the first upload of each
+        // frame, unless that upload alread overwrites the whole buffer.
+        if (!receivedData && clearBuffer != null) {
+            if (alphaMultiplier < 256 || start != 0 || pW != width || pH != height) {
+                clearBufferContents();
+            }
+        }
+        bb.position(address + pX * byteDepth + pY * width * 4);
+        bb.limit(bb.capacity());
+        // TODO: implement 16-bit framebuffer composition
+        // TODO: use a back buffer in Java when double buffering is not available in /dev/fb0
+        // TODO: move alpha blending into native
+        if (receivedData) {
+            IntBuffer srcPixels;
+            if (src instanceof IntBuffer) {
+                srcPixels = ((IntBuffer) src);
+            } else {
+                srcPixels = ((ByteBuffer) src).asIntBuffer();
+            }
+            IntBuffer dstPixels = bb.asIntBuffer();
+            for (int i = 0; i < pH; i++) {
+                int dstPosition = i * width;
+                int srcPosition = (start + i * stride) >> 2;
+                if (alphaMultiplier >= 255) {
+                    for (int j = 0; j < pW; j++) {
+                        int srcPixel = srcPixels.get(srcPosition + j);
+                        int srcA = (srcPixel >> 24) & 0xff;
+                        if (srcA == 0xff) {
+                            dstPixels.put(dstPosition + j, srcPixel);
+                        } else {
+                            dstPixels.put(dstPosition + j,
+                                          blend32(srcPixel,
+                                                  dstPixels.get(dstPosition + j),
+                                                  256));
+                        }
+                    }
+                } else {
+                    for (int j = 0; j < pW; j++) {
+                        dstPixels.put(dstPosition + j,
+                                      blend32(srcPixels.get(srcPosition + j),
+                                              dstPixels.get(dstPosition + j),
+                                              alphaMultiplier));
+                    }
+                }
+            }
+        } else {
+            if (pW == width) {
+                if (src instanceof ByteBuffer) {
+                    src.position(start);
+                    src.limit(stride * pH);
+                    bb.put((ByteBuffer) src);
+                } else {
+                    IntBuffer srcPixels = (IntBuffer) src;
+                    srcPixels.position(start >> 2);
+                    srcPixels.limit((stride * pH) >> 2);
+                    bb.asIntBuffer().put(srcPixels);
+                }
+            } else {
+                if (src instanceof ByteBuffer) {
+                    for (int i = 0; i < pH; i++) {
+                        bb.position(address + pX * 4 + (pY + i) * width * 4);
+                        src.limit(start + i * stride + pW * 4);
+                        src.position(start + i * stride);
+                        bb.put((ByteBuffer) src);
+                    }
+                } else {
+                    bb.position(address);
+                    bb.limit(address + width * height * byteDepth);
+                    IntBuffer dstPixels = bb.asIntBuffer();
+                    IntBuffer srcPixels = (IntBuffer) src;
+                    for (int i = 0; i < pH; i++) {
+//                        dstPixels.position((address >> 2) + pX + (pY + i) * width);
+                        dstPixels.position(pX + (pY + i) * width);
+                        srcPixels.limit(pW + ((start + i * stride) >> 2));
+                        srcPixels.position((start + i * stride) >> 2);
+                        dstPixels.put((IntBuffer) src);
+                    }
+                }
+            }
+        }
+        receivedData = true;
+    }
+
+    private static int blend32(int src, int dst, int alphaMultiplier) {
+        int srcA = (((src >> 24) & 0xff) * alphaMultiplier) >> 8;
+        int srcR = (src >> 16) & 0xff;
+        int srcG = (src >> 8) & 0xff;
+        int srcB = src & 0xff;
+        int dstA = (dst >> 24) & 0xff;
+        int dstR = (dst >> 16) & 0xff;
+        int dstG = (dst >> 8) & 0xff;
+        int dstB = dst & 0xff;
+        dstR = (srcR * srcA / 255) + (dstR * dstA * (255 - srcA) / 0xff00);
+        dstG = (srcG * srcA / 255) + (dstG * dstA * (255 - srcA) / 0xff00);
+        dstB = (srcB * srcA / 255) + (dstB * dstA * (255 - srcA) / 0xff00);
+        dstA = srcA + (dstA * (255 - srcA) / 0xff);
+        return (dstA << 24)| (dstR << 16) | (dstG << 8) | dstB;
+    }
+
+    public void write(FileChannel out) throws IOException {
+        bb.clear();
+        if (byteDepth == 4) {
+            out.write(bb);
+        } else if (byteDepth == 2) {
+            if (lineByteBuffer == null) {
+                lineByteBuffer = ByteBuffer.allocate(width * 2);
+                lineByteBuffer.order(ByteOrder.nativeOrder());
+                linePixelBuffer = lineByteBuffer.asShortBuffer();
+            }
+            IntBuffer srcPixels = bb.asIntBuffer();
+            ShortBuffer shortBuffer = (ShortBuffer) linePixelBuffer;
+            for (int i = 0; i < height; i++) {
+                shortBuffer.clear();
+                for (int j = 0; j < width; j++) {
+                    int pixel32 = srcPixels.get();
+                    int r = ((((pixel32 >> 19) & 31) * 539219) >> 8) & (31 << 11);
+                    int g = ((((pixel32 >> 10) & 63) * 265395) >> 13) & (63 << 5);
+                    int b = (((pixel32 >> 3) & 31) * 539219) >> 19;
+                    int pixel16 = r | g | b;
+                    shortBuffer.put((short) pixel16);
+                }
+                lineByteBuffer.clear();
+                out.write(lineByteBuffer);
+            }
+        }
+    }
+
+    public void copyToBuffer(ByteBuffer out) {
+        bb.clear();
+        if (byteDepth == 4) {
+            out.put(bb);
+        } else if (byteDepth == 2) {
+            if (lineByteBuffer == null) {
+                lineByteBuffer = ByteBuffer.allocate(width * 2);
+                lineByteBuffer.order(ByteOrder.nativeOrder());
+                linePixelBuffer = lineByteBuffer.asShortBuffer();
+            }
+            IntBuffer srcPixels = bb.asIntBuffer();
+            ShortBuffer shortBuffer = (ShortBuffer) linePixelBuffer;
+            for (int i = 0; i < height; i++) {
+                shortBuffer.clear();
+                for (int j = 0; j < width; j++) {
+                    int pixel32 = srcPixels.get();
+                    int r = ((((pixel32 >> 19) & 31) * 539219) >> 8) & (31 << 11);
+                    int g = ((((pixel32 >> 10) & 63) * 265395) >> 13) & (63 << 5);
+                    int b = (((pixel32 >> 3) & 31) * 539219) >> 19;
+                    int pixel16 = r | g | b;
+                    shortBuffer.put((short) pixel16);
+                }
+                lineByteBuffer.clear();
+                out.put(lineByteBuffer);
+            }
+        }
+    }
+
+}
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java	Tue Jan 21 02:49:34 2014 +0200
@@ -46,15 +46,12 @@
 import java.nio.IntBuffer;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 
 final class MonocleApplication extends Application {
 
     private final NativePlatform platform =
             NativePlatformFactory.getNativePlatform();
-    private final ExecutorService executor = platform.getExecutor();
+    private final RunnableProcessor runnableProcessor = platform.getRunnableProcessor();
 
     /** Bit to indicate that a device has touch support */
     private static final int DEVICE_TOUCH = 0;
@@ -71,6 +68,12 @@
     /** A running count of the numbers of devices with each device capability */
     private int[] deviceFlags = new int[DEVICE_MAX + 1];
 
+    private Runnable renderEndNotifier = new Runnable() {
+        public void run() {
+            platform.getScreen().swapBuffers();
+        }
+    };
+
     MonocleApplication() {
         platform.getInputDeviceRegistry().getInputDevices().addListener(
                 new SetChangeListener<InputDevice>() {
@@ -114,50 +117,34 @@
                 }
             }
         });
-        Runtime.getRuntime().addShutdownHook(new Thread() {
-            @Override public void run() {
-                platform.shutdown();
-            }
-        });
     }
 
     @Override
     protected void runLoop(Runnable launchable) {
-        Application.setEventThread(platform.getExecutorThread());
-        executor.submit(launchable);
+        runnableProcessor.invokeLater(launchable);
+        Thread t = new Thread(runnableProcessor);
+        setEventThread(t);
+        t.start();
     }
 
     @Override
     protected void _invokeAndWait(Runnable runnable) {
-        try {
-            executor.submit(new Callable<Void>() {
-                public Void call() {
-                    runnable.run();
-                    return null;
-                }
-            }).get();
-        } catch (InterruptedException e) {
-            // we are shutting down
-        } catch (ExecutionException e) {
-            e.printStackTrace();
-        }
+        runnableProcessor.invokeAndWait(runnable);
     }
 
     @Override
     protected void _invokeLater(Runnable runnable) {
-        executor.submit(runnable);
+        runnableProcessor.invokeLater(runnable);
     }
 
     @Override
     protected Object _enterNestedEventLoop() {
-        Thread.dumpStack();
-        throw new UnsupportedOperationException();
+        return runnableProcessor.enterNestedEventLoop();
     }
 
     @Override
     protected void _leaveNestedEventLoop(Object retValue) {
-        Thread.dumpStack();
-        throw new UnsupportedOperationException();
+        runnableProcessor.leaveNestedEventLoop(retValue);
     }
 
     @Override
@@ -362,4 +349,16 @@
         return deviceFlags[DEVICE_POINTER] > 0;
     }
 
+    @Override
+    public void notifyRenderingFinished() {
+        invokeLater(renderEndNotifier);
+    }
+
+    @Override
+    protected void finishTerminating() {
+        setEventThread(null);
+        platform.shutdown();
+        super.finishTerminating();
+    }
+
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleRobot.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleRobot.java	Tue Jan 21 02:49:34 2014 +0200
@@ -28,9 +28,13 @@
 import com.sun.glass.events.MouseEvent;
 import com.sun.glass.ui.Pixels;
 import com.sun.glass.ui.Robot;
+import com.sun.glass.ui.monocle.input.KeyInput;
+import com.sun.glass.ui.monocle.input.KeyState;
 import com.sun.glass.ui.monocle.input.MouseInput;
 import com.sun.glass.ui.monocle.input.MouseState;
 
+import java.nio.IntBuffer;
+
 public class MonocleRobot extends Robot {
     @Override
     protected void _create() {
@@ -42,12 +46,18 @@
 
     @Override
     protected void _keyPress(int code) {
-        // TODO: robot key press
+        KeyState state = new KeyState();
+        KeyInput.getInstance().getState(state);
+        state.pressKey(code);
+        KeyInput.getInstance().setState(state);
     }
 
     @Override
     protected void _keyRelease(int code) {
-        // TODO: robot key release
+        KeyState state = new KeyState();
+        KeyInput.getInstance().getState(state);
+        state.releaseKey(code);
+        KeyInput.getInstance().setState(state);
     }
 
     @Override
@@ -112,13 +122,29 @@
 
     @Override
     protected int _getPixelColor(int x, int y) {
-        return 0;
+        NativeScreen screen = NativePlatformFactory.getNativePlatform().getScreen();
+        IntBuffer buffer = screen.getScreenCapture();
+        return buffer.get(x + y * screen.getWidth());
     }
 
     @Override
     protected Pixels _getScreenCapture(int x, int y, int width, int height,
                                        boolean isHiDPI) {
-        // TODO: screen capture
-        return null;
+        NativeScreen screen = NativePlatformFactory.getNativePlatform().getScreen();
+        IntBuffer buffer = screen.getScreenCapture();
+        buffer.clear();
+        if (x == 0 && y == 0 && width == screen.getWidth() && height == screen.getHeight()) {
+            return new MonoclePixels(width, height, buffer);
+        } else {
+            IntBuffer selection = IntBuffer.allocate(width * height);
+            for (int i = 0; i < height; i++) {
+                int srcPos = x + (y + i) * screen.getWidth();
+                buffer.position(srcPos);
+                buffer.limit(srcPos + width);
+                selection.put(buffer);
+            }
+            selection.clear();
+            return new MonoclePixels(width, height, selection);
+        }
     }
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleView.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleView.java	Tue Jan 21 02:49:34 2014 +0200
@@ -31,8 +31,6 @@
 import com.sun.glass.ui.Window;
 
 import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
 import java.util.Map;
 
 public final class MonocleView extends View {
@@ -93,9 +91,10 @@
             NativeScreen screen =
                     NativePlatformFactory.getNativePlatform().getScreen();
             Window window = getWindow();
-            screen.uploadPixels(pixels.asByteBuffer(), // TODO: asByteBuffer is inefficient
+            screen.uploadPixels(pixels.getPixels(),
                                 x + window.getX(), y + window.getY(),
-                                pixels.getWidth(), pixels.getHeight());
+                                pixels.getWidth(), pixels.getHeight(),
+                                window.getAlpha());
         }
     }
 
@@ -103,63 +102,72 @@
      * Events
      */
 
-    public void _notifyMove(int x, int y) {
-        this.x = x;
-        this.y = y;
-        notifyView(ViewEvent.MOVE);
+    @Override
+    public void notifyKey(int type, int keyCode, char[] keyChars,
+                          int modifiers) {
+        super.notifyKey(type, keyCode, keyChars, modifiers);
     }
 
-    public void _notifyKey(int type, int keyCode, char[] keyChars,
-                              int modifiers) {
-        notifyKey(type, keyCode, keyChars, modifiers);
+    @Override
+    public void notifyMouse(int type, int button,
+                            int x, int y, int xAbs, int yAbs, int modifiers,
+                            boolean isPopupTrigger, boolean isSynthesized) {
+        super.notifyMouse(type, button, x, y, xAbs, yAbs, modifiers,
+                          isPopupTrigger,
+                          isSynthesized);
     }
 
-    public void _notifyMouse(int type, int button,
-                                int x, int y, int xAbs, int yAbs, int modifiers,
-                                boolean isPopupTrigger, boolean isSynthesized) {
-        notifyMouse(type, button, x, y, xAbs, yAbs, modifiers, isPopupTrigger,
-                    isSynthesized);
+    @Override
+    public void notifyScroll(int x, int y, int xAbs, int yAbs,
+                             double deltaX, double deltaY, int modifiers,
+                             int lines, int chars,
+                             int defaultLines, int defaultChars,
+                             double xMultiplier, double yMultiplier) {
+        super.notifyScroll(x, y, xAbs, yAbs, deltaX, deltaY,
+                           modifiers, lines, chars,
+                           defaultLines, defaultChars, xMultiplier,
+                           yMultiplier);
     }
 
-    public void _notifyScroll(int x, int y, int xAbs, int yAbs,
-                                 double deltaX, double deltaY, int modifiers,
-                                 int lines, int chars,
-                                 int defaultLines, int defaultChars,
-                                 double xMultiplier, double yMultiplier) {
-        notifyScroll(x, y, xAbs, yAbs, deltaX, deltaY,
-                     modifiers, lines, chars,
-                     defaultLines, defaultChars, xMultiplier, yMultiplier);
+    protected void notifyRepaint() {
+        super.notifyRepaint(x, y, getWidth(), getHeight());
     }
 
-    protected void _notifyRepaint(int x, int y, int width, int height) {
-        notifyRepaint(x, y, width, height);
+    @Override
+    protected void notifyResize(int width, int height) {
+        super.notifyResize(width, height);
     }
 
-    protected void _notifyResize(int width, int height) {
-        notifyResize(width, height);
-    }
-
-    protected void _notifyViewEvent(int viewEvent) {
-        notifyView(viewEvent);
+    @Override
+    protected void notifyView(int viewEvent) {
+        super.notifyView(viewEvent);
     }
 
     //DnD
-    protected void _notifyDragEnter(int x, int y, int absx, int absy, int recommendedDropAction) {
-        notifyDragEnter(x, y, absx, absy, recommendedDropAction);
+    @Override
+    protected int notifyDragEnter(int x, int y, int absx, int absy, int recommendedDropAction) {
+        return super.notifyDragEnter(x, y, absx, absy, recommendedDropAction);
     }
-    protected void _notifyDragLeave() {
-        notifyDragLeave();
+
+    @Override
+    protected void notifyDragLeave() {
+        super.notifyDragLeave();
     }
-    protected void _notifyDragDrop(int x, int y, int absx, int absy, int recommendedDropAction) {
-        notifyDragDrop(x, y, absx, absy, recommendedDropAction);
+
+    @Override
+    protected int notifyDragDrop(int x, int y, int absx, int absy, int recommendedDropAction) {
+        return super.notifyDragDrop(x, y, absx, absy, recommendedDropAction);
     }
-    protected void _notifyDragOver(int x, int y, int absx, int absy, int recommendedDropAction) {
-        notifyDragOver(x, y, absx, absy, recommendedDropAction);
+
+    @Override
+    protected int notifyDragOver(int x, int y, int absx, int absy, int recommendedDropAction) {
+        return super.notifyDragOver(x, y, absx, absy, recommendedDropAction);
     }
 
     //Menu event - i.e context menu hint (usually mouse right click) 
-    protected void _notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) {
-        notifyMenu(x, y, xAbs, yAbs, isKeyboardTrigger);
+    @Override
+    protected void notifyMenu(int x, int y, int xAbs, int yAbs, boolean isKeyboardTrigger) {
+        super.notifyMenu(x, y, xAbs, yAbs, isKeyboardTrigger);
     }
 
     @Override
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleWindow.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleWindow.java	Tue Jan 21 02:49:34 2014 +0200
@@ -25,16 +25,29 @@
 
 package com.sun.glass.ui.monocle;
 
+import com.sun.glass.events.ViewEvent;
 import com.sun.glass.events.WindowEvent;
 import com.sun.glass.ui.Cursor;
 import com.sun.glass.ui.Pixels;
+import com.sun.glass.ui.PlatformFactory;
 import com.sun.glass.ui.Screen;
 import com.sun.glass.ui.View;
 import com.sun.glass.ui.Window;
 
 public final class MonocleWindow extends Window {
 
+
+    private static final int STATE_NORMAL = 0;
+    private static final int STATE_MINIMIZED = 1;
+    private static final int STATE_MAXIMIZED = 2;
+    private static final int STATE_FULLSCREEN = 3;
+
     private int id;
+    private int state;
+    private int cachedX, cachedY, cachedW, cachedH;
+    private int minW, minH;
+    private int maxW = -1;
+    private int maxH = -1;
 
     protected MonocleWindow(Window owner, Screen screen, int styleMask) {
         super(owner, screen, styleMask);
@@ -80,34 +93,15 @@
                               int x, int y, boolean xSet, boolean ySet,
                               int w, int h, int cw, int ch,
                               float xGravity, float yGravity) {
-
-        //calculated window dimensions
         int width;
         int height;
 
-        //is new window size is the content size or the window size
-        //this required for platforms that support decorations.
-        //if isContentSize == true - width & height are
-        //the window size w/o decorations
-        boolean isContentSize = false;
-
-        //if false, only move window
-        boolean needResize = false;
-
-        if (w < 0 && h < 0 && cw < 0 && ch < 0) {
-            //nothing to do, return
-            return;
-        }
-
         if (w > 0) {
             //window width surpass window content width (cw)
             width = w;
-            needResize = true;
         } else if (cw > 0) {
             //content width changed
             width = cw;
-            isContentSize = true;
-            needResize = true;
         } else {
             //no explicit request to change width, get default
             width = getWidth();
@@ -116,12 +110,9 @@
         if (h > 0) {
             //window height surpass window content height(ch)
             height = h;
-            needResize = true;
         } else if (cw > 0) {
             //content height changed
             height = ch;
-            isContentSize = true;
-            needResize = true;
         } else {
             //no explicit request to change height, get default
             height = getHeight();
@@ -132,32 +123,38 @@
         if (!ySet) {
             y = getY();
         }
+        if (maxW >= 0) {
+            width = Math.min(width, maxW);
+        }
+        if (maxH >= 0) {
+            height = Math.min(height, maxH);
+        }
+        width = Math.max(width, minW);
+        height = Math.max(height, minH);
 
+        notifyResizeAndMove(x, y, width, height);
+    }
 
-        //perform actions
-        boolean windowHasBeenUpdated = false;
-        needResize |= isContentSize;
+    private void notifyResizeAndMove(int x, int y, int width, int height) {
+        MonocleView view = (MonocleView) getView();
+        boolean repaintView = false;
 
-        //handle resize if needed
-        if (needResize &&
-            (getWidth() != width || getHeight() != height)) {
-
+        if (getWidth() != width || getHeight() != height) {
             notifyResize(WindowEvent.RESIZE, width, height);
-
-            windowHasBeenUpdated = true;
-
+            if (view != null) {
+                view.notifyResize(width, height);
+                repaintView = true;
+            }
         }
-
-        //handle move if needed
         if (getX() != x || getY() != y) {
-            notifyMove(x, y);       
-
-            windowHasBeenUpdated = true;
-
-            //TODO: do we need repaints?
-            //lens_wm_repaint(env, window);
+            notifyMove(x, y);
+            if (view != null) {
+                repaintView = true;
+            }
         }
-
+        if (repaintView) {
+            view.notifyRepaint();
+        }
     }
 
     //creates the native window
@@ -184,7 +181,7 @@
         if (view != null) {
             // the system assumes a resize notification to set the View
             // sizes and to get the Scene to layout correctly.
-            ((MonocleView)view)._notifyResize(getWidth(), getHeight());
+            ((MonocleView)view).notifyResize(getWidth(), getHeight());
         }
         return result;
     }
@@ -204,13 +201,89 @@
 
     @Override
     protected boolean _minimize(long nativeWindowPointer, boolean minimize) {
-        return MonocleWindowManager.getInstance().minimizeWindow(this);
+        if (minimize) {
+            state = STATE_MINIMIZED;
+        } else {
+            state = STATE_NORMAL;
+        }
+        return true;
     }
 
     @Override
     protected boolean _maximize(long nativeWindowPointer, boolean maximize,
                                 boolean wasMaximized) {
-        return MonocleWindowManager.getInstance().maximizeWindow(this);
+        NativeScreen screen = NativePlatformFactory.getNativePlatform().getScreen();
+        int x = getX();
+        int y = getY();
+        int width = getWidth();
+        int height = getHeight();
+        if (maximize && !wasMaximized) {
+            if (state == STATE_NORMAL) {
+                cachedX = x;
+                cachedY = y;
+                cachedW = width;
+                cachedH = height;
+            }
+            if (maxW >= 0) {
+                width = maxW;
+                x = Math.min(x, screen.getWidth() - width);
+            } else {
+                x = 0;
+                width = screen.getWidth();
+            }
+            if (maxH >= 0) {
+                height = maxH;
+                y = Math.min(y, screen.getHeight() - height);
+            } else {
+                y = 0;
+                height = screen.getHeight();
+            }
+            state = STATE_MAXIMIZED;
+        } else if (!maximize && wasMaximized) {
+            x = cachedX;
+            y = cachedY;
+            width = cachedW;
+            height = cachedH;
+            state = STATE_NORMAL;
+        }
+        notifyResizeAndMove(x, y, width, height);
+        return true;
+    }
+
+    void setFullScreen(boolean fullscreen) {
+        NativeScreen screen = NativePlatformFactory.getNativePlatform().getScreen();
+        int x = getX();
+        int y = getY();
+        int width = getWidth();
+        int height = getHeight();
+        if (fullscreen) {
+            if (state == STATE_NORMAL) {
+                cachedX = x;
+                cachedY = y;
+                cachedW = width;
+                cachedH = height;
+            }
+            x = 0;
+            y = 0;
+            width = screen.getWidth();
+            height = screen.getHeight();
+            MonocleView view = (MonocleView) getView();
+            if (view != null) {
+                view.notifyView(ViewEvent.FULLSCREEN_ENTER);
+            }
+            state = STATE_FULLSCREEN;
+        } else {
+            x = cachedX;
+            y = cachedY;
+            width = cachedW;
+            height = cachedH;
+            MonocleView view = (MonocleView) getView();
+            if (view != null) {
+                view.notifyView(ViewEvent.FULLSCREEN_EXIT);
+            }
+            state = STATE_NORMAL;
+        }
+        notifyResizeAndMove(x, y, width, height);
     }
 
     private float cachedAlpha = 1;
@@ -260,11 +333,15 @@
 
     @Override
     protected boolean _setMinimumSize(long ptr, int width, int height) {
+        minW = width;
+        minH = height;
         return true;
     }
 
     @Override
     protected boolean _setMaximumSize(long ptr, int width, int height) {
+        maxW = width;
+        maxH = height;
         return true;
     }
 
@@ -303,47 +380,19 @@
         throw new UnsupportedOperationException();
     }
 
-    //**************************************************************
-    // wrappers so Application run loop can get where it needs to go
-    protected void _notifyClose() {
-        //This event is called by MonocleWindowManager when a window needs to be
-        //closed, so this is a synthetic way to emulate platform window manager
-        //window close event
-        notifyClose();
-        close();
+    @Override
+    protected void notifyClose() {
+        super.notifyClose();
     }
 
-    protected void _notifyDestroy() {
-        notifyDestroy();
+    @Override
+    protected void notifyDestroy() {
+        super.notifyDestroy();
     }
 
-    protected void _notifyFocus(int event) {
-        notifyFocus(event);
-    }
-
-    protected void _notifyMove(final int x, final int y) {
-        notifyMove(x, y);
-        // Note! we don't notify the view of a move
-        // as the view is only supposed to have decoration
-        // offsets for X and Y. If we have those, call
-        // view._notifyMove(x,y) directly with the offsets
-    }
-
-    protected void _notifyResize(final int type, final int width,
-                                 final int height) {
-        notifyResize(type, width, height);
-        MonocleView view = (MonocleView) getView();
-        if (view != null) {
-            view._notifyResize(width, height);
-        }
-    }
-
-    protected void _notifyExpose(final int x, final int y, final int width,
-                                 final int height) {
-        MonocleView view = (MonocleView) getView();
-        if (view != null) {
-            view._notifyRepaint(x, y, width, height);
-        }
+    @Override
+    protected void notifyFocus(int event) {
+        super.notifyFocus(event);
     }
 
     protected void _notifyFocusUngrab() {
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleWindowManager.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleWindowManager.java	Tue Jan 21 02:49:34 2014 +0200
@@ -26,8 +26,11 @@
 package com.sun.glass.ui.monocle;
 
 import com.sun.glass.events.WindowEvent;
+import com.sun.glass.ui.Window;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 public final class MonocleWindowManager {
 
@@ -91,6 +94,16 @@
                              windows.length - index - 1);
             windows = Arrays.copyOf(windows, windows.length - 1);
         }
+        List<MonocleWindow> windowsToNotify = new ArrayList<MonocleWindow>();
+        for (MonocleWindow otherWindow : windows) {
+            if (otherWindow.getOwner() == window) {
+                windowsToNotify.add(otherWindow);
+            }
+        }
+        for (int i = 0; i < windowsToNotify.size(); i++) {
+            windowsToNotify.get(i).notifyClose();
+        }
+        window.notifyDestroy();
         return true;
 
     }
@@ -107,7 +120,7 @@
         int index = getWindowIndex(window);
         if (index != -1) {
             focusedWindow = window;
-            window._notifyFocus(WindowEvent.FOCUS_GAINED);
+            window.notifyFocus(WindowEvent.FOCUS_GAINED);
             return true;
         } else {
             return false;
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/NativePlatform.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/NativePlatform.java	Tue Jan 21 02:49:34 2014 +0200
@@ -27,39 +27,19 @@
 
 import com.sun.glass.ui.monocle.input.InputDeviceRegistry;
 
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
 public abstract class NativePlatform {
 
     private static InputDeviceRegistry inputDeviceRegistry;
-    protected final ExecutorService executor;
-    private Thread executorThread;
+    protected final RunnableProcessor runnableProcessor;
     private NativeCursor cursor;
     private NativeScreen screen;
 
     protected NativePlatform() {
-        this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-            @Override
-            public Thread newThread(Runnable r) {
-                executorThread = new Thread(r, "Monocle Application Thread");
-                return executorThread;
-            }
-        });
-        // Get the event thread started so that its thread exists when we ask
-        // for it in getExecutorThread(). This causes newThread() to run
-        // immediately, in the current thread. So once submit() returns,
-        // executorThread has been initialized.
-        executor.submit(new Runnable() {
-            public void run() {
-            }
-        });
-        assert(executorThread != null);
+        runnableProcessor = new RunnableProcessor();
     }
 
     protected void shutdown() {
-        executor.shutdown();
+        runnableProcessor.shutdown();
         if (cursor != null) {
             cursor.shutdown();
         }
@@ -68,12 +48,8 @@
         }
     }
 
-    public ExecutorService getExecutor() {
-        return executor;
-    }
-
-    public Thread getExecutorThread() {
-        return executorThread;
+    public RunnableProcessor getRunnableProcessor() {
+        return runnableProcessor;
     }
 
     public synchronized InputDeviceRegistry getInputDeviceRegistry() {
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/NativeScreen.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/NativeScreen.java	Tue Jan 21 02:49:34 2014 +0200
@@ -25,7 +25,8 @@
 
 package com.sun.glass.ui.monocle;
 
-import java.nio.ByteBuffer;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
 
 public interface NativeScreen {
 
@@ -37,9 +38,11 @@
     public long getNativeHandle();
     public void shutdown();
 
-    public void uploadPixels(ByteBuffer b,
-                             int x, int y, int width, int height);
+    public void uploadPixels(Buffer b,
+                             int x, int y, int width, int height, float alpha);
 
     public void swapBuffers();
 
+    public IntBuffer getScreenCapture();
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/RunnableProcessor.java	Tue Jan 21 02:49:34 2014 +0200
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import com.sun.glass.ui.Application;
+
+import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
+
+public class RunnableProcessor implements Runnable {
+
+    private RunnableQueue queue = new RunnableQueue();
+
+    private static class RunLoopControl {
+        boolean active; // thread should continue to process events.
+        Object release; // object to return with on leave nested
+    }
+
+    // our stack of nested run loops
+    private LinkedList<RunLoopControl> activeRunLoops = new LinkedList<RunLoopControl>();
+
+    @Override
+    public void run() {
+        runLoop();
+    }
+
+    public void invokeLater(Runnable r) {
+        queue.postRunnable(r);
+    }
+
+    public void invokeAndWait(final Runnable r) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        queue.postRunnable(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    r.run();
+                } finally {
+                    latch.countDown();
+                }
+            }
+        });
+        try {
+            latch.await();
+        } catch (InterruptedException e) { }
+    }
+
+    private Object runLoop() {
+        final RunLoopControl control = new RunLoopControl();
+
+        //push this new instance on the stack
+        activeRunLoops.push(control);
+
+        control.active = true;
+        while (control.active) {
+            try {
+                queue.getNextRunnable().run();
+            } catch (Throwable e) {
+                Application.reportException(e);
+            }
+        }
+
+        return control.release;
+
+    }
+
+    Object enterNestedEventLoop() {
+        // we are being called on the current active event thread
+        // via dispatch, so it is stalled until we return.
+
+        // start our nested loop, which will block until that exits
+        Object ret = runLoop();
+
+        // and return the value that was passed into leaveNested
+        return ret;
+    }
+
+    void leaveNestedEventLoop(Object retValue) {
+        // we are being called from dispatch of the current running
+        // event thread. We want to cause this thread to exit, and
+        // restart the nested on.
+
+        RunLoopControl current = activeRunLoops.pop();
+        assert current != null;
+
+        // let the current run loop die when we return to dispatch.
+        current.active = false;
+        // and give it the ret object so it will return it to the
+        // blocked nesting call.
+        current.release = retValue;
+
+        // when we return from this dispatched event, we will exit
+        // because we are no longer active, and then the nested
+        // call can return the release value we just provided.
+    }
+
+    void shutdown() {
+        synchronized (queue) {
+            queue.clear();
+            while (!activeRunLoops.isEmpty()) {
+                RunLoopControl control = activeRunLoops.pop();
+                control.active = false;
+            }
+            queue.notifyAll();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/RunnableQueue.java	Tue Jan 21 02:49:34 2014 +0200
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+
+class RunnableQueue {
+
+    private Runnable[] queue = new Runnable[32];
+    private int start;
+    private int count;
+
+    private int modulo(int index) {
+        if (index >= queue.length) {
+            index -= queue.length;
+        }
+        return index;
+    }
+
+    synchronized void postRunnable(Runnable r) {
+        if (count == queue.length) {
+            Runnable[] newQueue = new Runnable[(queue.length * 3) / 2];
+            System.arraycopy(queue, start, newQueue, 0, queue.length - start);
+            System.arraycopy(queue, 0, newQueue, queue.length - start, start);
+            queue = newQueue;
+            start = 0;
+        }
+        queue[modulo(start + count)] = r;
+        count ++;
+        notifyAll();
+    }
+
+    synchronized Runnable getNextRunnable() throws InterruptedException {
+        while (count == 0) {
+            wait();
+        }
+        Runnable r = queue[start];
+        queue[start] = null;
+        start = modulo(start + 1);
+        count --;
+        return r;
+    }
+
+    synchronized void clear() {
+        Arrays.fill(queue, null);
+        count = 0;
+    }
+
+}
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/headless/HeadlessScreen.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/headless/HeadlessScreen.java	Tue Jan 21 02:49:34 2014 +0200
@@ -26,9 +26,13 @@
 package com.sun.glass.ui.monocle.headless;
 
 import com.sun.glass.ui.Pixels;
+import com.sun.glass.ui.monocle.Framebuffer;
 import com.sun.glass.ui.monocle.NativeScreen;
 
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 
@@ -37,6 +41,7 @@
     private int depth = 32;
     private int width = 1280;
     private int height = 800;
+    private Framebuffer fb;
 
     public HeadlessScreen() {
         String geometry = AccessController.doPrivileged(new PrivilegedAction<String>() {
@@ -49,17 +54,26 @@
             try {
                 int i = geometry.indexOf("x");
                 width = Integer.parseInt(geometry.substring(0, i));
-                height = Integer.parseInt(geometry.substring(i + 1));
+                int j = geometry.indexOf("-", i + 1);
+                if (j > 0) {
+                    depth = Integer.parseInt(geometry.substring(j + 1));
+                } else {
+                    j = geometry.length();
+                }
+                height = Integer.parseInt(geometry.substring(i + 1, j));
             } catch (NumberFormatException e) {
                 System.err.println("Cannot parse geometry string: '"
                         + geometry + "'");
             }
         }
+        ByteBuffer bb = ByteBuffer.allocate(width * height * (depth >>> 3));
+        bb.order(ByteOrder.nativeOrder());
+        fb = new Framebuffer(bb, width, height, depth, true);
     }
 
     @Override
     public int getDepth() {
-        return 32;
+        return depth;
     }
 
     @Override
@@ -92,11 +106,20 @@
     }
 
     @Override
-    public void uploadPixels(ByteBuffer b, int x, int y, int width, int height) {
+    public void uploadPixels(Buffer b,
+                             int x, int y, int width, int height,
+                             float alpha) {
+        fb.composePixels(b, x, y, width, height, alpha);
     }
 
     @Override
     public void swapBuffers() {
+        fb.reset();
+    }
+
+    @Override
+    public IntBuffer getScreenCapture() {
+        return fb.getBuffer().asIntBuffer();
     }
 
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/KeyInput.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/KeyInput.java	Tue Jan 21 02:49:34 2014 +0200
@@ -103,9 +103,9 @@
             return;
         }
         char[] chars = getKeyChars(ks, key);
-        view._notifyKey(type, key, chars, ks.getModifiers());
+        view.notifyKey(type, key, chars, ks.getModifiers());
         if (type == KeyEvent.PRESS && chars.length > 0) {
-            view._notifyKey(KeyEvent.TYPED, key, chars, ks.getModifiers());
+            view.notifyKey(KeyEvent.TYPED, key, chars, ks.getModifiers());
         }
     }
 
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/MouseInput.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/input/MouseInput.java	Tue Jan 21 02:49:34 2014 +0200
@@ -76,9 +76,9 @@
                 int oldY = state.getY();
                 int oldRelX = oldX - oldWindow.getX();
                 int oldRelY = oldY - oldWindow.getY();
-                oldView._notifyMouse(MouseEvent.EXIT, button,
-                                  oldRelX, oldRelY, oldX, oldY,
-                                  modifiers, isPopupTrigger, synthesized);
+                oldView.notifyMouse(MouseEvent.EXIT, button,
+                                    oldRelX, oldRelY, oldX, oldY,
+                                    modifiers, isPopupTrigger, synthesized);
             }
         }
         boolean newAbsoluteLocation = state.getX() != x || state.getY() != y;
@@ -97,9 +97,9 @@
             int modifiers = state.getModifiers(); // TODO: include key modifiers
             int button = state.getButton();
             boolean isPopupTrigger = false; // TODO
-            view._notifyMouse(MouseEvent.ENTER, button,
-                              relX, relY, x, y,
-                              modifiers, isPopupTrigger, synthesized);
+            view.notifyMouse(MouseEvent.ENTER, button,
+                             relX, relY, x, y,
+                             modifiers, isPopupTrigger, synthesized);
         }
         // send motion events
         if (oldWindow != window | newAbsoluteLocation) {
@@ -108,9 +108,9 @@
             int modifiers = state.getModifiers(); // TODO: include key modifiers
             int button = state.getButton();
             boolean isPopupTrigger = false; // TODO
-            view._notifyMouse(eventType, button,
-                              relX, relY, x, y,
-                              modifiers, isPopupTrigger, synthesized);
+            view.notifyMouse(eventType, button,
+                             relX, relY, x, y,
+                             modifiers, isPopupTrigger, synthesized);
         }
         // send press events
         newState.getButtonsPressed().difference(buttons, state.getButtonsPressed());
@@ -122,10 +122,10 @@
                 pressState.pressButton(button);
                 // send press event
                 boolean isPopupTrigger = false; // TODO
-                view._notifyMouse(MouseEvent.DOWN, button,
-                                  relX, relY, x, y,
-                                  pressState.getModifiers(), isPopupTrigger,
-                                  synthesized);
+                view.notifyMouse(MouseEvent.DOWN, button,
+                                 relX, relY, x, y,
+                                 pressState.getModifiers(), isPopupTrigger,
+                                 synthesized);
             }
         }
         buttons.clear();
@@ -140,10 +140,10 @@
                 releaseState.releaseButton(button);
                 // send release event
                 boolean isPopupTrigger = false; // TODO
-                view._notifyMouse(MouseEvent.UP, button,
-                                  relX, relY, x, y,
-                                  releaseState.getModifiers(), isPopupTrigger,
-                                  synthesized);
+                view.notifyMouse(MouseEvent.UP, button,
+                                 relX, relY, x, y,
+                                 releaseState.getModifiers(), isPopupTrigger,
+                                 synthesized);
 
             }
         }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/FBDevScreen.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/FBDevScreen.java	Tue Jan 21 02:49:34 2014 +0200
@@ -26,10 +26,14 @@
 package com.sun.glass.ui.monocle.linux;
 
 import com.sun.glass.ui.Pixels;
+import com.sun.glass.ui.monocle.Framebuffer;
 import com.sun.glass.ui.monocle.NativeScreen;
 
 import java.io.IOException;
+import java.nio.Buffer;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
 import java.nio.channels.FileChannel;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
@@ -37,21 +41,18 @@
 
 public class FBDevScreen implements NativeScreen {
 
-    private int depth;
     private int nativeFormat;
-    protected int width;
-    protected int height;
     private long nativeHandle;
     private FileChannel fbdev;
+    private ByteBuffer mappedFB;
     private boolean isShutdown;
     private int consoleCursorBlink;
+    private Framebuffer fb;
+    private LinuxFrameBuffer linuxFB;
 
     public FBDevScreen() {
         try {
-            depth = SysFS.readInt("/sys/class/graphics/fb0/bits_per_pixel");
-            int[] vsize = SysFS.readInts("/sys/class/graphics/fb0/virtual_size", 2);
-            width = vsize[0];
-            height = vsize[1];
+            linuxFB = new LinuxFrameBuffer("/dev/fb0");
             nativeHandle = 1l;
             nativeFormat = Pixels.Format.BYTE_BGRA_PRE;
             try {
@@ -73,7 +74,7 @@
 
     @Override
     public int getDepth() {
-        return depth;
+        return linuxFB.getDepth();
     }
 
     @Override
@@ -83,12 +84,12 @@
 
     @Override
     public int getWidth() {
-        return width;
+        return linuxFB.getWidth();
     }
 
     @Override
     public int getHeight() {
-        return height;
+        return linuxFB.getHeight();
     }
 
     @Override
@@ -101,39 +102,59 @@
         return 96; // no way to read DPI from sysfs and ioctl returns junk values
     }
 
-    private void openFBDev() throws IOException {
-        Path fbdevPath = FileSystems.getDefault().getPath("/dev/fb0");
-        fbdev = FileChannel.open(fbdevPath, StandardOpenOption.WRITE);
+    private boolean isFBDevOpen() {
+        return mappedFB != null || fbdev != null;
     }
 
-    private void clearFBDev() {
-        ByteBuffer b = ByteBuffer.allocate(width * depth >> 3);
-        try {
-            for (int i = 0; i < height; i++) {
-                b.position(0);
-                b.limit(b.capacity());
-                fbdev.position(i * width * depth >> 3);
-                fbdev.write(b);
+    private void openFBDev() throws IOException {
+        if (mappedFB == null) {
+            Path fbdevPath = FileSystems.getDefault().getPath("/dev/fb0");
+            fbdev = FileChannel.open(fbdevPath, StandardOpenOption.WRITE);
+        }
+    }
+
+    private void closeFBDev() {
+        if (mappedFB != null) {
+            linuxFB.releaseMappedBuffer(mappedFB);
+            mappedFB = null;
+        } else if (fbdev != null) {
+            try {
+                fbdev.close();
+            } catch (IOException e) { }
+            fbdev = null;
+        }
+        linuxFB.close();
+    }
+
+    private Framebuffer getFramebuffer() {
+        // The Framebuffer obect must be created lazily. If we are running with
+        // the ES2 pipeline then we won't need the framebuffer until shutdown time.
+        if (fb == null) {
+            ByteBuffer bb;
+            mappedFB = linuxFB.getMappedBuffer();
+            if (mappedFB != null) {
+                bb = mappedFB;
+            } else {
+                bb = ByteBuffer.allocateDirect(getWidth() * getHeight() * 4);
             }
-        } catch (IOException e) {
-            e.printStackTrace();
+            bb.order(ByteOrder.nativeOrder());
+            fb = new Framebuffer(bb, getWidth(), getHeight(), getDepth(), true);
+            fb.setStartAddress(linuxFB.getNextAddress());
         }
+        return fb;
     }
 
     @Override
     public synchronized void shutdown() {
+        getFramebuffer().clearBufferContents();
         try {
-            if (fbdev == null) {
-                openFBDev();
-            }
-            if (fbdev != null) {
-                clearFBDev();
-                fbdev.close();
+            if (isFBDevOpen()) {
+                writeBuffer();
+                closeFBDev();
             }
         } catch (IOException e) {
             e.printStackTrace();
         } finally {
-            fbdev = null;
             isShutdown = true;
         }
         if (consoleCursorBlink != 0) {
@@ -146,39 +167,47 @@
     }
 
     @Override
-    public synchronized void uploadPixels(ByteBuffer b,
-                             int pX, int pY, int pWidth, int pHeight) {
-        if (isShutdown) {
-            return;
-        }
-        // TODO: Handle 16-bit screens and window composition
+    public synchronized void uploadPixels(Buffer b,
+                             int pX, int pY, int pWidth, int pHeight,
+                             float alpha) {
+        getFramebuffer().composePixels(b, pX, pY, pWidth, pHeight, alpha);
+    }
+
+    @Override
+    public synchronized void swapBuffers() {
         try {
-            if (fbdev == null) {
-                openFBDev();
-                clearFBDev();
+            if (isShutdown || fb == null || !getFramebuffer().hasReceivedData()) {
+                return;
             }
-            if (width == pWidth) {
-                b.limit(pWidth * 4 * pHeight);
-                fbdev.position((pY * width + pX) * (depth >> 3));
-                fbdev.write(b);
-            } else {
-                for (int i = 0; i < pHeight; i++) {
-                    int position = i * pWidth * (depth >> 3);
-                    b.position(position);
-                    b.limit(position + pWidth * 4);
-                    fbdev.position(((i + pY) * width + pX) * (depth >> 3));
-                    fbdev.write(b);
-                }
-            }
+            writeBuffer();
         } catch (IOException e) {
             e.printStackTrace();
+        } finally {
+            getFramebuffer().reset();
+        }
+    }
+
+    private synchronized void writeBuffer() throws IOException {
+        if (!linuxFB.isDoubleBuffer()) {
+            linuxFB.vSync();
+        }
+        if (mappedFB == null) {
+            if (!isFBDevOpen()) {
+                openFBDev();
+            }
+            fbdev.position(linuxFB.getNextAddress());
+            getFramebuffer().write(fbdev);
+        } else if (linuxFB.isDoubleBuffer()) {
+            linuxFB.next();
+            linuxFB.vSync();
+            getFramebuffer().setStartAddress(linuxFB.getNextAddress());
         }
     }
 
     @Override
-    public void swapBuffers() {
-        // TODO: We could double-buffer here if the virtual screen size is at
-        // least twice the visible size
+    public synchronized IntBuffer getScreenCapture() {
+        getFramebuffer().getBuffer().clear();
+        return getFramebuffer().getBuffer().asIntBuffer();
     }
 
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/GetEvent.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/GetEvent.java	Tue Jan 21 02:49:34 2014 +0200
@@ -37,7 +37,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.Callable;
 
 /** A test entry point for Monacle to show what events are read from input
  * devices.
@@ -103,13 +102,7 @@
         Udev.getInstance().addListener(udevListener);
         // Request updates for existing devices
         SysFS.triggerUdevNotification("input");
-        // Make sure the executor is started. Otherwise we will exit at the
-        // end of this method, since all other threads are daemons.
-        platform.getExecutor().submit(new Callable<Void>() {
-            public Void call() {
-                return null;
-            }
-        }).get();
+        new Thread(platform.getRunnableProcessor()).start();
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxFrameBuffer.java	Tue Jan 21 02:49:34 2014 +0200
@@ -0,0 +1,174 @@
+/*
+ * 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.linux;
+
+import com.sun.glass.ui.monocle.util.C;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class LinuxFrameBuffer {
+
+    private long fd;
+    private LinuxSystem system;
+    private LinuxSystem.FbVarScreenInfo screenInfo;
+    private int width;
+    private int height;
+    private int bitDepth;
+    private int byteDepth;
+    private int offsetX, offsetY;
+    private int offsetY1, offsetY2;
+    private int offsetX1, offsetX2;
+    private int state; // 0 = single buffer, 1 = showing buf 1, 2 = showing buf 2
+    private int FBIO_WAITFORVSYNC;
+
+
+    public LinuxFrameBuffer(String devNode) throws IOException {
+        system = LinuxSystem.getLinuxSystem();
+        FBIO_WAITFORVSYNC = system.IOW('F', 0x20, 4);
+        fd = system.open("/dev/fb0", LinuxSystem.O_RDWR);
+        if (fd == -1) {
+            throw new IOException(system.getErrorMessage());
+        }
+        screenInfo = new LinuxSystem.FbVarScreenInfo();
+        if (system.ioctl(fd, LinuxSystem.FBIOGET_VSCREENINFO, screenInfo.p) != 0) {
+            system.close(fd);
+            throw new IOException(system.getErrorMessage());
+        }
+        bitDepth = screenInfo.getBitsPerPixel(screenInfo.p);
+        byteDepth = bitDepth >>> 3;
+        width = screenInfo.getXRes(screenInfo.p);
+        height = screenInfo.getYRes(screenInfo.p);
+        int virtualWidth = screenInfo.getXResVirtual(screenInfo.p);
+        int virtualHeight = screenInfo.getYResVirtual(screenInfo.p);
+        offsetX = screenInfo.getOffsetX(screenInfo.p);
+        offsetY = screenInfo.getOffsetY(screenInfo.p);
+        if (virtualHeight >= height * 2) {
+            if (offsetY >= height) {
+                offsetY1 = offsetY;
+                offsetY2 = 0;
+            } else if (virtualHeight - offsetY >= height * 2) {
+                offsetY1 = offsetY;
+                offsetY2 = offsetY + height;
+            } else {
+                offsetY1 = 0;
+                offsetY2 = width * byteDepth;
+            }
+            offsetX1 = offsetX2 = offsetX;
+            state = 1;
+        } else if (virtualWidth >= width * 2) {
+            if (offsetX >= width) {
+                offsetX1 = offsetX;
+                offsetX2 = 0;
+            } else if (virtualWidth - offsetX >= width * 2) {
+                offsetX1 = offsetX;
+                offsetX2 = offsetX + height;
+            } else {
+                offsetX1 = 0;
+                offsetX2 = width * byteDepth;
+            }
+            offsetY1 = offsetY2 = offsetY;
+            state = 1;
+        }
+    }
+
+    public int getNextAddress() {
+        switch (state) {
+            case 1:
+                return (offsetX2 + offsetY2 * width) * byteDepth;
+            case 2:
+                return (offsetX1 + offsetY1 * width) * byteDepth;
+            default:
+                return (offsetX + offsetY * width) * byteDepth;
+        }
+    }
+
+    public void next() throws IOException {
+        if (state != 0) {
+            int newOffsetX, newOffsetY;
+            if (state == 1) {
+                newOffsetX = offsetX2;
+                newOffsetY = offsetY2;
+            } else {
+                newOffsetX = offsetX1;
+                newOffsetY = offsetY1;
+            }
+            screenInfo.setActivate(screenInfo.p, LinuxSystem.FB_ACTIVATE_VBL);
+            screenInfo.setOffset(screenInfo.p, newOffsetX, newOffsetY);
+            if (system.ioctl(fd, LinuxSystem.FBIOPAN_DISPLAY, screenInfo.p) != 0) {
+                // turn off double-buffering and don't try to pan again
+                state = 0;
+                throw new IOException(system.getErrorMessage());
+            } else {
+                offsetX = newOffsetX;
+                offsetY = newOffsetY;
+                state = 3 - state;
+            }
+        }
+    }
+
+    public void vSync() {
+        system.ioctl(fd, FBIO_WAITFORVSYNC, 0);
+    }
+
+    public ByteBuffer getMappedBuffer() {
+        int mappedFBSize = screenInfo.getXResVirtual(screenInfo.p)
+                * screenInfo.getYResVirtual(screenInfo.p)
+                * byteDepth;
+        long addr = system.mmap(0l, mappedFBSize,
+                                LinuxSystem.PROT_WRITE,
+                                LinuxSystem.MAP_SHARED, fd, 0);
+        if (addr != LinuxSystem.MAP_FAILED) {
+            return C.getC().NewDirectByteBuffer(addr, mappedFBSize);
+        }
+        return null;
+    }
+
+    public void releaseMappedBuffer(ByteBuffer b) {
+        system.munmap(C.getC().GetDirectBufferAddress(b), b.capacity());
+    }
+
+    public void close() {
+        system.close(fd);
+    }
+
+    public boolean isDoubleBuffer() {
+        return state > 0;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getDepth() {
+        return bitDepth;
+    }
+
+}
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDevice.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputDevice.java	Tue Jan 21 02:49:34 2014 +0200
@@ -27,26 +27,21 @@
 
 import com.sun.glass.ui.Application;
 import com.sun.glass.ui.monocle.NativePlatformFactory;
+import com.sun.glass.ui.monocle.RunnableProcessor;
 import com.sun.glass.ui.monocle.input.InputDevice;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
-import java.nio.file.StandardOpenOption;
 import java.util.BitSet;
 import java.util.Map;
-import java.util.concurrent.ExecutorService;
 
 /**
  * A LinuxInputDevice listens for events on a Linux
  * input device node, typically one of the files in /dev/input. When events are
  * waiting to be processed on the device it notifies its listener on a thread
- * provided by its executor object. The executor should be a single-threaded
- * ExecutorService that runs all tasks on the JavaFX application thread.
+ * provided by its runnable processor object.
  * <p>
  * Event lines are accumulated in a buffer until an event "EV_SYN EV_SYN_REPORT
  * 0" is received. At this point the listener is notified. The listener can then
@@ -67,7 +62,7 @@
     private Map<Integer, AbsoluteInputCapabilities> absCaps;
     private Map<String, String> udevManifest;
     private ByteBuffer event = ByteBuffer.allocateDirect(LinuxEventBuffer.EVENT_STRUCT_SIZE);
-    private ExecutorService executor;
+    private RunnableProcessor runnableProcessor;
     private EventProcessor processor = new EventProcessor();
     private LinuxEventBuffer buffer = new LinuxEventBuffer();
     private Map<String,String> uevent;
@@ -98,7 +93,8 @@
         // attempt to grab the device. If the grab fails, keep going.
         int EVIOCGRAB = system.IOW('E', 0x90, 4);
         system.ioctl(fd, EVIOCGRAB, 1);
-        this.executor = NativePlatformFactory.getNativePlatform().getExecutor();
+        this.runnableProcessor = NativePlatformFactory.getNativePlatform()
+                .getRunnableProcessor();
         this.uevent = SysFS.readUEvent(sysPath);
     }
 
@@ -121,7 +117,8 @@
         this.in = in;
         this.udevManifest = udevManifest;
         this.uevent = uevent;
-        this.executor = NativePlatformFactory.getNativePlatform().getExecutor();
+        this.runnableProcessor = NativePlatformFactory.getNativePlatform()
+                .getRunnableProcessor();
     }
 
     public void setInputProcessor(LinuxInputProcessor inputProcessor) {
@@ -155,7 +152,7 @@
                     event.flip();
                     synchronized (buffer) {
                         if (buffer.put(event) && !processor.scheduled) {
-                            executor.submit(processor);
+                            runnableProcessor.invokeLater(processor);
                             processor.scheduled = true;
                         }
                     }
@@ -170,7 +167,7 @@
 
     /**
      * The EventProcessor is used to notify listeners of pending events. It runs
-     * on the executor thread.
+     * on the application thread.
      */
     class EventProcessor implements Runnable {
         boolean scheduled;
@@ -187,7 +184,7 @@
             synchronized (buffer) {
                 if (buffer.hasNextEvent()) {
                     // a new event came in after the call to processEvents
-                    executor.submit(processor);
+                    runnableProcessor.invokeLater(processor);
                 } else {
                     processor.scheduled = false;
                 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputProcessor.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxInputProcessor.java	Tue Jan 21 02:49:34 2014 +0200
@@ -34,7 +34,7 @@
 public interface LinuxInputProcessor {
     /**
      * Called when events are waiting on the input device to be processed.
-     * Called on the executor provided to the input device.
+     * Called on the runnable processor provided to the input device.
      *
      * @param device The device on which events are pending
      */
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxSystem.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/LinuxSystem.java	Tue Jan 21 02:49:34 2014 +0200
@@ -94,9 +94,12 @@
 
     public static final int FBIOGET_VSCREENINFO = 0x4600;
     public static final int FBIOPUT_VSCREENINFO = 0x4601;
+    public static final int FBIOPAN_DISPLAY = 0x4606;
     public static final int FBIOBLANK = 0x4611;
 
     public static final int FB_BLANK_UNBLANK = 0;
+    public static final int FB_ACTIVATE_NOW = 0;
+    public static final int FB_ACTIVATE_VBL = 16;
 
     public static class FbVarScreenInfo extends C.Structure {
         public FbVarScreenInfo() {
@@ -104,8 +107,13 @@
         }
         @Override
         public native int sizeof();
+        public native int getBitsPerPixel(long p);
         public native int getXRes(long p);
         public native int getYRes(long p);
+        public native int getXResVirtual(long p);
+        public native int getYResVirtual(long p);
+        public native int getOffsetX(long p);
+        public native int getOffsetY(long p);
         public native void setRes(long p, int x, int y);
         public native void setVirtualRes(long p, int x, int y);
         public native void setOffset(long p, int x, int y);
@@ -145,6 +153,15 @@
     public native long dlsym(long handle, String symbol);
     public native int dlclose(long handle);
 
+    // mman.h
+    public static final long PROT_READ = 0x1l;
+    public static final long PROT_WRITE = 0x2l;
+    public static final long MAP_SHARED = 0x1l;
+    public static final long MAP_FAILED = 0xffffffffl;
+    public native long mmap(long addr, long length, long prot, long flags,
+                            long fd, long offset);
+    public native int munmap(long addr, long length);
+
     public String getErrorMessage() {
         return strerror(errno());
     }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/Udev.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/Udev.java	Tue Jan 21 02:49:34 2014 +0200
@@ -26,6 +26,7 @@
 package com.sun.glass.ui.monocle.linux;
 
 import com.sun.glass.ui.monocle.NativePlatformFactory;
+import com.sun.glass.ui.monocle.RunnableProcessor;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -33,7 +34,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.concurrent.ExecutorService;
 
 /**
  * Udev connects to the udev system to get updates on sysfs devices that
@@ -92,11 +92,11 @@
     @Override
     public void run() {
         try {
-            ExecutorService executor =
-                    NativePlatformFactory.getNativePlatform().getExecutor();
+            RunnableProcessor runnableProcessor =
+                    NativePlatformFactory.getNativePlatform().getRunnableProcessor();
             while (true) {
                 Map<String, String> event = readEvent();
-                executor.submit(new Runnable() {
+                runnableProcessor.invokeLater(new Runnable() {
                     public void run() {
                         String action = event.get("ACTION");
                         if (action != null) {
@@ -109,10 +109,13 @@
                                     try {
                                         uls[i].udevEvent(action, event);
                                     } catch (RuntimeException e) {
-                                        System.err.println("Exception in udev listener:");
+                                        System.err.println(
+                                                "Exception in udev listener:");
                                         e.printStackTrace();
                                     } catch (Error e) {
-                                        System.err.println("Error in udev listener, closing udev");
+                                        System.err.println(
+                                                "Error in udev listener, " +
+                                                        "closing udev");
                                         e.printStackTrace();
                                         close();
                                         return;
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/UdevListener.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/linux/UdevListener.java	Tue Jan 21 02:49:34 2014 +0200
@@ -36,7 +36,7 @@
 
     /**
      * Called when a udev event is available.
-     * Called on the executor provided to the Udev.
+     * Called on the runnable processor provided to the Udev.
      *
      * @param action The udev action, usually "add", "remove", "change",
      *               "online" or "offline.
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/omap/OMAPScreen.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/omap/OMAPScreen.java	Tue Jan 21 02:49:34 2014 +0200
@@ -32,18 +32,4 @@
 
 public class OMAPScreen extends FBDevScreen {
 
-    public OMAPScreen() {
-        try {
-            // OMAP can report a larger vertical screen size in
-            // /sys/class/graphics/fb0/virtual_size than the physical size. So
-            // we read the real size here.
-            int[] size = SysFS.readInts("/sys/devices/platform/omapdss/overlay0/input_size", 2);
-            width = size[0];
-            height = size[1];
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw (IllegalStateException)
-                    new IllegalStateException().initCause(e);
-        }
-    }
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/util/C.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/util/C.java	Tue Jan 21 02:49:34 2014 +0200
@@ -26,24 +26,44 @@
 package com.sun.glass.ui.monocle.util;
 
 import java.nio.ByteBuffer;
+import java.security.Permission;
 
 public class C {
 
+    private static Permission permission = new RuntimePermission("loadLibrary.*");
+
+    private static C instance = new C();
+
+    public static C getC() {
+        checkPermissions();
+        return instance;
+    }
+
+    private static void checkPermissions() {
+        SecurityManager security = System.getSecurityManager();
+        if (security != null) {
+            security.checkPermission(permission);
+        }
+    }
+
+    private C() {
+    }
+
     public static abstract class Structure {
         public final ByteBuffer b;
         public final long p;
         protected Structure() {
             b = ByteBuffer.allocateDirect(sizeof());
-            p = GetDirectBufferAddress(b);
+            p = getC().GetDirectBufferAddress(b);
         }
         protected Structure(long ptr) {
-            b = NewDirectByteBuffer(ptr, sizeof());
+            b = getC().NewDirectByteBuffer(ptr, sizeof());
             p = ptr;
         }
         public abstract int sizeof();
     }
 
-    private static native ByteBuffer NewDirectByteBuffer(long ptr, int size);
-    private static native long GetDirectBufferAddress(ByteBuffer b);
+    public native ByteBuffer NewDirectByteBuffer(long ptr, int size);
+    public native long GetDirectBufferAddress(ByteBuffer b);
 
 }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/x11/X11InputDeviceRegistry.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/x11/X11InputDeviceRegistry.java	Tue Jan 21 02:49:34 2014 +0200
@@ -28,13 +28,12 @@
 import com.sun.glass.events.MouseEvent;
 import com.sun.glass.ui.monocle.NativePlatform;
 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.InputDeviceRegistry;
 import com.sun.glass.ui.monocle.input.MouseInput;
 import com.sun.glass.ui.monocle.input.MouseState;
 
-import java.util.concurrent.ExecutorService;
-
 class X11InputDeviceRegistry extends InputDeviceRegistry {
 
     private MouseState state;
@@ -73,8 +72,9 @@
                 X11Screen screen = (X11Screen) platform.getScreen();
                 long display = screen.getDisplay();
                 long window = screen.getNativeHandle();
-                ExecutorService executor = platform.getExecutor();
-                executor.submit(new Runnable() {
+                RunnableProcessor runnableProcessor =
+                        platform.getRunnableProcessor();
+                runnableProcessor.invokeLater(new Runnable() {
                     public void run() {
                         devices.add(device);
                     }
@@ -86,7 +86,7 @@
                     if (X.XEvent.getWindow(event.p) != window) {
                         continue;
                     }
-                    processXEvent(event, executor);
+                    processXEvent(event, runnableProcessor);
                 }
             }
         });
@@ -95,25 +95,27 @@
         x11InputThread.start();
     }
 
-    private void processXEvent(X.XEvent event, ExecutorService executor) {
+    private void processXEvent(X.XEvent event,
+                               RunnableProcessor runnableProcessor) {
         switch (X.XEvent.getType(event.p)) {
             case X.ButtonPress: {
                 X.XButtonEvent buttonEvent = new X.XButtonEvent(event);
                 int button = X.XButtonEvent.getButton(buttonEvent.p);
-                executor.submit(new ButtonPressProcessor(button));
+                runnableProcessor.invokeLater(new ButtonPressProcessor(button));
                 break;
             }
             case X.ButtonRelease: {
                 X.XButtonEvent buttonEvent = new X.XButtonEvent(event);
                 int button = X.XButtonEvent.getButton(buttonEvent.p);
-                executor.submit(new ButtonReleaseProcessor(button));
+                runnableProcessor.invokeLater(
+                        new ButtonReleaseProcessor(button));
                 break;
             }
             case X.MotionNotify: {
                 X.XMotionEvent motionEvent = new X.XMotionEvent(event);
                 int x = X.XMotionEvent.getX(motionEvent.p);
                 int y = X.XMotionEvent.getY(motionEvent.p);
-                executor.submit(new MotionProcessor(x, y));
+                runnableProcessor.invokeLater(new MotionProcessor(x, y));
                 break;
             }
         }
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/x11/X11Screen.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/x11/X11Screen.java	Tue Jan 21 02:49:34 2014 +0200
@@ -28,7 +28,8 @@
 import com.sun.glass.ui.Pixels;
 import com.sun.glass.ui.monocle.NativeScreen;
 
-import java.nio.ByteBuffer;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 
@@ -159,11 +160,18 @@
     }
 
     @Override
-    public void uploadPixels(ByteBuffer b, int x, int y, int width, int height) {
+    public void uploadPixels(Buffer b,
+                             int x, int y, int width, int height,
+                             float alpha) {
         // TODO: upload pixels to X11 window
     }
 
     @Override
     public void swapBuffers() {
     }
+
+    @Override
+    public IntBuffer getScreenCapture() {
+        return null;
+    }
 }
--- a/modules/graphics/src/main/native-glass/monocle/linux/LinuxSystem.c	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/native-glass/monocle/linux/LinuxSystem.c	Tue Jan 21 02:49:34 2014 +0200
@@ -36,6 +36,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -154,6 +155,11 @@
     return (jint) sizeof(struct fb_var_screeninfo);
 }
 
+JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_getBitsPerPixel
+  (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p) {
+    return (jint) ((struct fb_var_screeninfo *) asPtr(p))->bits_per_pixel;
+}
+
 JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_getXRes
   (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p) {
     return (jint) ((struct fb_var_screeninfo *) asPtr(p))->xres;
@@ -164,6 +170,28 @@
     return (jint) ((struct fb_var_screeninfo *) asPtr(p))->yres;
 }
 
+JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_getXResVirtual
+  (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p) {
+    return (jint) ((struct fb_var_screeninfo *) asPtr(p))->xres_virtual;
+}
+
+JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_getYResVirtual
+  (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p) {
+    return (jint) ((struct fb_var_screeninfo *) asPtr(p))->yres_virtual;
+}
+
+JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_getOffsetX
+  (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p) {
+    return (jint) ((struct fb_var_screeninfo *) asPtr(p))->xoffset;
+}
+
+
+JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_getOffsetY
+  (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p) {
+    return (jint) ((struct fb_var_screeninfo *) asPtr(p))->yoffset;
+}
+
+
 JNIEXPORT void JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_00024FbVarScreenInfo_setRes
   (JNIEnv *UNUSED(env), jobject UNUSED(obj), jlong p, int x, int y) {
     struct fb_var_screeninfo *screen = (struct fb_var_screeninfo *) asPtr(p);
@@ -259,3 +287,15 @@
   (JNIEnv *UNUSED(env), jclass UNUSED(cls), jlong p) {
     return (jint) ((struct input_absinfo *) asPtr(p))->resolution;
 }
+
+JNIEXPORT jlong JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_mmap
+  (JNIEnv *env, jobject UNUSED(obj), jlong addr, long length, jlong prot,
+        jlong flags, jlong fd, jlong offset) {
+    return asJLong(mmap(asPtr(addr), (size_t) length, (int) prot, (int) flags,
+                        (int) fd, (off_t) offset));
+}
+
+JNIEXPORT jint JNICALL Java_com_sun_glass_ui_monocle_linux_LinuxSystem_munmap
+  (JNIEnv *env, jobject UNUSED(obj), jlong addr, long length) {
+    return (jint) munmap(asPtr(addr), (size_t) length);
+}
--- a/modules/graphics/src/main/native-glass/monocle/util/C.c	Tue Jan 21 10:24:00 2014 +1300
+++ b/modules/graphics/src/main/native-glass/monocle/util/C.c	Tue Jan 21 02:49:34 2014 +0200
@@ -28,14 +28,14 @@
 
 JNIEXPORT jobject JNICALL
  Java_com_sun_glass_ui_monocle_util_C_NewDirectByteBuffer
-    (JNIEnv *env, jclass UNUSED(cClass), jlong ptr, jint size) {
+    (JNIEnv *env, jobject UNUSED(obj), jlong ptr, jint size) {
     return (*env)->NewDirectByteBuffer(env,
             (void *) (unsigned long) ptr, (jlong) size);
 }
 
 JNIEXPORT jlong JNICALL
  Java_com_sun_glass_ui_monocle_util_C_GetDirectBufferAddress
-    (JNIEnv *env, jclass UNUSED(cClass), jobject byteBuffer) {
+    (JNIEnv *env, jobject UNUSED(obj), jobject byteBuffer) {
     return byteBuffer
             ? asJLong((*env)->GetDirectBufferAddress(env, byteBuffer))
             : (jlong) 0l;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/headless/HeadlessGeometry1Test.java	Tue Jan 21 02:49:34 2014 +0200
@@ -0,0 +1,74 @@
+/*
+ * 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.headless;
+
+import com.sun.glass.ui.Screen;
+import javafx.application.Application;
+import javafx.stage.Stage;
+import junit.framework.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class HeadlessGeometry1Test {
+
+    private static CountDownLatch startupLatch = new CountDownLatch(1);
+
+    private static int width;
+    private static int height;
+    private static int depth;
+
+    public static class TestApp extends Application {
+        @Override
+        public void start(Stage t) {
+            width = Screen.getMainScreen().getWidth();
+            height = Screen.getMainScreen().getHeight();
+            depth = Screen.getMainScreen().getDepth();
+            startupLatch.countDown();
+        }
+    }
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        System.setProperty("glass.platform", "Monocle");
+        System.setProperty("monocle.platform", "Headless");
+        System.setProperty("prism.order", "sw");
+        System.setProperty("headless.geometry", "150x250");
+        new Thread(() -> Application.launch(TestApp.class)).start();
+        startupLatch.await(5, TimeUnit.SECONDS);
+        Assert.assertEquals(0, startupLatch.getCount());
+    }
+
+    @Test
+    public void setScreenBounds() throws Exception {
+        Assert.assertEquals(150, width);
+        Assert.assertEquals(250, height);
+        Assert.assertEquals(32, depth);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/headless/HeadlessGeometry2Test.java	Tue Jan 21 02:49:34 2014 +0200
@@ -0,0 +1,74 @@
+/*
+ * 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.headless;
+
+import com.sun.glass.ui.Screen;
+import javafx.application.Application;
+import javafx.stage.Stage;
+import junit.framework.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class HeadlessGeometry2Test {
+
+    private static CountDownLatch startupLatch = new CountDownLatch(1);
+
+    private static int width;
+    private static int height;
+    private static int depth;
+
+    public static class TestApp extends Application {
+        @Override
+        public void start(Stage t) {
+            width = Screen.getMainScreen().getWidth();
+            height = Screen.getMainScreen().getHeight();
+            depth = Screen.getMainScreen().getDepth();
+            startupLatch.countDown();
+        }
+    }
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        System.setProperty("glass.platform", "Monocle");
+        System.setProperty("monocle.platform", "Headless");
+        System.setProperty("prism.order", "sw");
+        System.setProperty("headless.geometry", "150x250-16");
+        new Thread(() -> Application.launch(TestApp.class)).start();
+        startupLatch.await(5, TimeUnit.SECONDS);
+        Assert.assertEquals(0, startupLatch.getCount());
+    }
+
+    @Test
+    public void setScreenBounds() throws Exception {
+        Assert.assertEquals(150, width);
+        Assert.assertEquals(250, height);
+        Assert.assertEquals(16, depth);
+    }
+
+}
--- a/tests/system/src/test/java/com/sun/glass/ui/monocle/input/EGalaxTest.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/EGalaxTest.java	Tue Jan 21 02:49:34 2014 +0200
@@ -257,7 +257,7 @@
         final int y1 = (int) Math.round(screen.getHeight() / 2.0);
         final int x2 = (int) Math.round(screen.getWidth() / 3.0);
         final int y2 = (int) Math.round(screen.getHeight() / 2.0);
-        
+
         TestLog.reset();
         // first finger
         ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
@@ -350,5 +350,83 @@
         TestLog.waitForLog("Mouse released: " + x2 + ", " + y2, 3000l);
         TestLog.waitForLog("Touch released: " + x2 + ", " + y2, 3000l);        
     }
-}
 
+    /** Test that double taps in the same area generate synthesized
+     * multi-click mouse events. */
+    @Test
+    public void testDoubleClick1() throws Exception {
+        int x = (int) Math.round(screen.getWidth() / 2.0);
+        int y = (int) Math.round(screen.getHeight() / 2.0);
+        TestApplication.getStage().getScene().setOnMouseClicked((e) -> {
+            TestLog.format("Mouse clicked: %d, %d: clickCount %d",
+                           (int) e.getScreenX(), (int) e.getScreenY(),
+                           e.getClickCount());
+        });
+        TestLog.reset();
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 1");
+        absMTPosition(x, y);
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 0");
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 1");
+        absMTPosition(x, y);
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 0");
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        TestLog.waitForLog("Mouse clicked: " + x + ", " + y + ": clickCount 1", 3000l);
+        TestLog.waitForLog("Mouse clicked: " + x + ", " + y + ": clickCount 2", 3000l);
+    }
+
+    @Test
+    public void testDoubleClick2() throws Exception {
+        int x1 = (int) Math.round(screen.getWidth() / 2.0);
+        int y1 = (int) Math.round(screen.getHeight() / 2.0);
+        int x2 = x1 + TestApplication.getTapRadius();
+        int y2 = y1 + TestApplication.getTapRadius();
+
+        TestApplication.getStage().getScene().setOnMouseClicked((e) -> {
+            TestLog.format("Mouse clicked: %d, %d: clickCount %d",
+                           (int) e.getScreenX(), (int) e.getScreenY(),
+                           e.getClickCount());
+        });
+        TestLog.reset();
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 1");
+        absMTPosition(x1, y1);
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 0");
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 1");
+        absMTPosition(x2, y2);
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        ui.processLine("EV_ABS ABS_MT_TRACKING_ID 0");
+        ui.processLine("EV_ABS ABS_MT_TOUCH_MAJOR 0");
+        ui.processLine("EV_SYN SYN_MT_REPORT 0");
+        ui.processLine("EV_SYN SYN_REPORT 0");
+
+        TestLog.waitForLog("Mouse clicked: " + x1 + ", " + y1 + ": clickCount 1", 3000l);
+        TestLog.waitForLog("Mouse clicked: " + x2 + ", " + y2 + ": clickCount 2", 3000l);
+    }
+
+ }
+
--- a/tests/system/src/test/java/com/sun/glass/ui/monocle/input/TestLog.java	Tue Jan 21 10:24:00 2014 +1300
+++ b/tests/system/src/test/java/com/sun/glass/ui/monocle/input/TestLog.java	Tue Jan 21 02:49:34 2014 +0200
@@ -27,7 +27,9 @@
 
 import junit.framework.AssertionFailedError;
 
+import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Formatter;
 import java.util.List;
 
 public class TestLog {
@@ -46,6 +48,10 @@
         }
     }
 
+    public static void format(String format, Object... args) {
+        log(new Formatter().format(format, args).toString());
+    }
+
     public static String[] getLog() {
         return log.toArray(new String[log.size()]);
     }
@@ -169,10 +175,14 @@
                 if (timeNow >= endTime) {
                     String message = "Timed out after " + (timeNow - startTime)
                             + "ms waiting for '" + s + "'";
-                    if (TestApplication.isVerbose()) {
-                        TestLog.log(message);
+                    if (!TestApplication.isVerbose()) {
+                        System.out.flush();
+                        System.err.flush();
+                        for (String logLine: log) {
+                            System.out.println(logLine);
+                        }
                     }
-                    throw new RuntimeException(message);
+                    throw new AssertionFailedError(message);
                 }
             }
         }