changeset 6156:ebf7575491e0

RT-35441 [Monocle] Provide a VNC back-end
author Daniel Blaukopf <daniel.blaukopf@oracle.com>
date Tue, 21 Jan 2014 15:43:01 +0200
parents 96ad554b62b8
children 1428564df8ed
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/headless/HeadlessScreen.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/vnc/VNCPlatform.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/vnc/VNCPlatformFactory.java modules/graphics/src/main/java/com/sun/glass/ui/monocle/vnc/VNCScreen.java
diffstat 6 files changed, 458 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/Framebuffer.java	Tue Jan 21 07:43:58 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/Framebuffer.java	Tue Jan 21 15:43:01 2014 +0200
@@ -31,7 +31,7 @@
 import java.nio.ByteOrder;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
-import java.nio.channels.FileChannel;
+import java.nio.channels.WritableByteChannel;
 
 public class Framebuffer {
 
@@ -56,6 +56,7 @@
     }
 
     public ByteBuffer getBuffer() {
+        bb.clear();
         return bb;
     }
 
@@ -177,7 +178,6 @@
                     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);
@@ -205,7 +205,7 @@
         return (dstA << 24)| (dstR << 16) | (dstG << 8) | dstB;
     }
 
-    public void write(FileChannel out) throws IOException {
+    public void write(WritableByteChannel out) throws IOException {
         bb.clear();
         if (byteDepth == 4) {
             out.write(bb);
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java	Tue Jan 21 07:43:58 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/MonocleApplication.java	Tue Jan 21 15:43:01 2014 +0200
@@ -75,6 +75,9 @@
     };
 
     MonocleApplication() {
+        for (InputDevice device : platform.getInputDeviceRegistry().getInputDevices()) {
+            updateDeviceFlags(device, true);
+        }
         platform.getInputDeviceRegistry().getInputDevices().addListener(
                 new SetChangeListener<InputDevice>() {
             @Override
@@ -82,43 +85,34 @@
                     Change<? extends InputDevice> change) {
                 if (change.wasAdded()) {
                     InputDevice device = change.getElementAdded();
-                    if (device.isTouch()) {
-                        deviceFlags[DEVICE_TOUCH] ++;
-                    }
-                    if (device.isMultiTouch()) {
-                        deviceFlags[DEVICE_MULTITOUCH] ++;
-                    }
-                    if (device.isRelative()) {
-                        deviceFlags[DEVICE_POINTER] ++;
-                    }
-                    if (device.isFullKeyboard()) {
-                        deviceFlags[DEVICE_PC_KEYBOARD] ++;
-                    }
-                    if (device.is5Way()) {
-                        deviceFlags[DEVICE_5WAY] ++;
-                    }
+                    updateDeviceFlags(device, true);
                 } else if (change.wasRemoved()) {
                     InputDevice device = change.getElementRemoved();
-                    if (device.isTouch()) {
-                        deviceFlags[DEVICE_TOUCH] --;
-                    }
-                    if (device.isMultiTouch()) {
-                        deviceFlags[DEVICE_MULTITOUCH] --;
-                    }
-                    if (device.isRelative()) {
-                        deviceFlags[DEVICE_POINTER] --;
-                    }
-                    if (device.isFullKeyboard()) {
-                        deviceFlags[DEVICE_PC_KEYBOARD] --;
-                    }
-                    if (device.is5Way()) {
-                        deviceFlags[DEVICE_5WAY] --;
-                    }
+                    updateDeviceFlags(device, false);
                 }
             }
         });
     }
 
+    private void updateDeviceFlags(InputDevice device, boolean added) {
+        int modifier = added ? 1 : -1;
+        if (device.isTouch()) {
+            deviceFlags[DEVICE_TOUCH] += modifier;
+        }
+        if (device.isMultiTouch()) {
+            deviceFlags[DEVICE_MULTITOUCH] += modifier;
+        }
+        if (device.isRelative()) {
+            deviceFlags[DEVICE_POINTER] += modifier;
+        }
+        if (device.isFullKeyboard()) {
+            deviceFlags[DEVICE_PC_KEYBOARD] += modifier;
+        }
+        if (device.is5Way()) {
+            deviceFlags[DEVICE_5WAY] += modifier;
+        }
+    }
+
     @Override
     protected void runLoop(Runnable launchable) {
         runnableProcessor.invokeLater(launchable);
--- a/modules/graphics/src/main/java/com/sun/glass/ui/monocle/headless/HeadlessScreen.java	Tue Jan 21 07:43:58 2014 +0200
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/headless/HeadlessScreen.java	Tue Jan 21 15:43:01 2014 +0200
@@ -38,12 +38,21 @@
 
 public class HeadlessScreen implements NativeScreen {
 
-    private int depth = 32;
-    private int width = 1280;
-    private int height = 800;
-    private Framebuffer fb;
+    protected int depth;
+    protected int width;
+    protected int height;
+    protected Framebuffer fb;
 
     public HeadlessScreen() {
+        this(1280, 800, 32);
+    }
+
+    protected HeadlessScreen(int defaultWidth,
+                             int defaultHeight,
+                             int defaultDepth) {
+        this.width = defaultWidth;
+        this.height = defaultHeight;
+        this.depth = defaultDepth;
         String geometry = AccessController.doPrivileged(new PrivilegedAction<String>() {
             @Override
             public String run() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/vnc/VNCPlatform.java	Tue Jan 21 15:43:01 2014 +0200
@@ -0,0 +1,56 @@
+package com.sun.glass.ui.monocle.vnc;/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.glass.ui.monocle.NativeScreen;
+import com.sun.glass.ui.monocle.headless.HeadlessPlatform;
+import com.sun.glass.ui.monocle.input.InputDevice;
+import com.sun.glass.ui.monocle.input.InputDeviceRegistry;
+
+public class VNCPlatform extends HeadlessPlatform {
+
+    @Override
+    protected InputDeviceRegistry createInputDeviceRegistry() {
+        InputDeviceRegistry registry = new InputDeviceRegistry() {
+            {
+                // Register a pointing device so that the virtual keyboard will
+                // be used
+                devices.add(new InputDevice() {
+                    @Override public boolean isTouch() { return true; }
+                    @Override public boolean isMultiTouch() { return false; }
+                    @Override public boolean isRelative() { return false; }
+                    @Override public boolean is5Way() { return false; }
+                    @Override public boolean isFullKeyboard() { return false; }
+                });
+            }
+        };
+        return registry;
+    }
+
+    @Override
+    protected NativeScreen createScreen() {
+        return new VNCScreen();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/vnc/VNCPlatformFactory.java	Tue Jan 21 15:43:01 2014 +0200
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.vnc;
+
+import com.sun.glass.ui.monocle.NativePlatform;
+import com.sun.glass.ui.monocle.NativePlatformFactory;
+
+public class VNCPlatformFactory extends NativePlatformFactory {
+
+    @Override
+    protected boolean matches() {
+        return true;
+    }
+
+    @Override
+    protected NativePlatform createNativePlatform() {
+        return new VNCPlatform();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/java/com/sun/glass/ui/monocle/vnc/VNCScreen.java	Tue Jan 21 15:43:01 2014 +0200
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.glass.ui.monocle.vnc;
+
+import com.sun.glass.events.MouseEvent;
+import com.sun.glass.ui.monocle.headless.HeadlessScreen;
+import com.sun.glass.ui.monocle.input.MouseInput;
+import com.sun.glass.ui.monocle.input.MouseState;
+import javafx.application.Platform;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.util.BitSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/** A headless screen that is available for remote connections using the
+ * RFB 3.3 protocol on port 5901.
+ */
+public class VNCScreen extends HeadlessScreen {
+
+    private ServerSocketChannel server;
+    private Set<ClientConnection> clients = new HashSet<ClientConnection>();
+
+    public VNCScreen() {
+        super(1024, 600, 32);
+        try {
+            server = ServerSocketChannel.open();
+            server.bind(new InetSocketAddress(5901));
+            Thread t = new Thread(new ConnectionAccepter());
+            t.setDaemon(true);
+            t.setName("VNC Server on port 5901");
+            t.start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        super.shutdown();
+        for (ClientConnection cc : clients) {
+            try {
+                cc.socket.close();
+            } catch (IOException e) { }
+        }
+    }
+
+    @Override
+    public void swapBuffers() {
+        ClientConnection[] ccs;
+        synchronized (clients) {
+            ccs = clients.toArray(new ClientConnection[clients.size()]);
+        }
+        for (ClientConnection cc : ccs) {
+            try {
+                sendBuffer(cc.socket);
+            } catch (IOException e) {
+                clients.remove(cc);
+            }
+        }
+        super.swapBuffers();
+    }
+
+    private void removeClient(ClientConnection cc, IOException e) {
+        synchronized (clients) {
+            if (clients.contains(cc)) {
+                System.out.format("Disconnecting %s: %s\n",
+                                  cc.descriptor, e.getMessage());
+                clients.remove(cc);
+            }
+        }
+    }
+
+    private void sendBuffer(WritableByteChannel out) throws IOException {
+        ByteBuffer buffer = ByteBuffer.allocate(16);
+        buffer.order(ByteOrder.BIG_ENDIAN);
+        buffer.put((byte) 0);
+        buffer.put((byte) 0);
+        buffer.putShort((short) 1); // rectangle count
+        buffer.putShort((short) 0); // x
+        buffer.putShort((short) 0); // y
+        buffer.putShort((short) width);
+        buffer.putShort((short) height);
+        buffer.putInt(0); // raw
+        buffer.flip();
+        out.write(buffer);
+        fb.write(out);
+    }
+
+    private class ConnectionAccepter implements Runnable {
+        @Override
+        public void run() {
+            ByteBuffer buffer = ByteBuffer.allocate(64);
+            buffer.order(ByteOrder.BIG_ENDIAN);
+            while (true) {
+                try {
+                    SocketChannel client = server.accept();
+                    System.out.format("Connection received from %s\n",
+                                      client.getRemoteAddress());
+                    // Declare the server protocol version
+                    buffer.clear();
+                    buffer.put("RFB 003.003\n".getBytes());
+                    buffer.flip();
+                    client.write(buffer);
+                    // Read the client protocol version
+                    buffer.clear();
+                    buffer.limit(12);
+                    client.read(buffer);
+                    buffer.flip();
+                    System.out.format("Client supports %s\n",
+                                      Charset.forName("UTF-8")
+                                              .decode(buffer).toString().trim());
+                    buffer.clear();
+                    buffer.putInt(1); // no authentication
+                    buffer.flip();
+                    client.write(buffer);
+                    buffer.clear();
+                    buffer.limit(1);
+                    client.read(buffer);
+                    System.out.format("Client share request: %d\n",
+                                      buffer.get(0));
+                    buffer.clear();
+                    buffer.putShort((short) width);
+                    buffer.putShort((short) height);
+                    buffer.put((byte) depth);
+                    buffer.put((byte) depth);
+                    buffer.put((byte) (ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN) ? 0 : 1));
+                    buffer.put((byte) 1); // true color
+                    if (depth == 32) {
+                        buffer.putShort((short) 255); // red max
+                        buffer.putShort((short) 255); // green max
+                        buffer.putShort((short) 255); // blue max
+                        buffer.put((byte) 16); // red offset
+                        buffer.put((byte) 8); // blue offset
+                        buffer.put((byte) 0); // green offset
+                    } else {
+                        buffer.putShort((byte) (short) 31);
+                        buffer.putShort((byte) (short) 63);
+                        buffer.putShort((byte) (short) 31);
+                        buffer.put((byte) 11);
+                        buffer.put((byte) 5);
+                        buffer.put((byte) 0);
+                    }
+                    buffer.put((byte) 0); // padding
+                    buffer.put((byte) 0);
+                    buffer.put((byte) 0);
+                    String name = "JavaFX on " + client.getLocalAddress();
+                    buffer.putInt(name.length());
+                    buffer.put(name.getBytes());
+                    buffer.flip();
+                    client.write(buffer);
+                    ClientConnection cc = new ClientConnection();
+                    cc.socket = client;
+                    Thread t = new Thread(cc);
+                    t.setDaemon(true);
+                    t.setName("VNC client connection from "
+                                      + client.getRemoteAddress());
+                    t.start();
+                    synchronized (clients) {
+                        clients.add(cc);
+                    }
+                    sendBuffer(client);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private class ClientConnection implements Runnable {
+        private SocketChannel socket;
+        private String descriptor;
+        @Override
+        public void run() {
+            ByteBuffer buffer = ByteBuffer.allocate(32);
+            buffer.order(ByteOrder.BIG_ENDIAN);
+            try {
+                descriptor = socket.getRemoteAddress().toString();
+                while (true) {
+                    buffer.clear();
+                    buffer.limit(4);
+                    socket.read(buffer);
+                    switch (buffer.get(0)) {
+                        case 0: // SetPixelFormat
+                            // discard the message in the next 16 bytes
+                            buffer.clear();
+                            buffer.limit(16);
+                            socket.read(buffer);
+                            break;
+                        case 1: // FixColorMapEntries
+                            buffer.clear();
+                            buffer.limit(2);
+                            socket.read(buffer);
+                            // discard color map entries
+                            int colorMapEntryCount = buffer.getShort(0);
+                            for (int i = 0; i < colorMapEntryCount; i++) {
+                                buffer.clear();
+                                buffer.limit(6);
+                                socket.read(buffer);
+                            }
+                            break;
+                        case 2: // SetEncodings
+                            // discard encodings
+                            int encodingCount = buffer.getShort(2);
+                            for (int i = 0; i < encodingCount; i++) {
+                                buffer.clear();
+                                buffer.limit(4);
+                                socket.read(buffer);
+                            }
+                        case 3: // FramebufferUpdateRequest
+                            buffer.clear();
+                            buffer.limit(6);
+                            socket.read(buffer);
+                            Platform.runLater(new Runnable() {
+                                @Override
+                                public void run() {
+                                    try {
+                                        if (fb.hasReceivedData()) {
+                                            // an update is in progress and will
+                                            // be sent on the next call to
+                                            // swapBuffers. No need to
+                                            // respond to this request.
+                                        } else {
+                                            sendBuffer(socket);
+                                        }
+                                    } catch (IOException e) {
+                                        removeClient(ClientConnection.this, e);
+                                    }
+                                }
+                            });
+                            break;
+                        case 4: // KeyEvent
+                            buffer.clear();
+                            buffer.limit(4);
+                            socket.read(buffer);
+                            break;
+                        case 5: { // PointerEvent
+                            int x = buffer.getShort(2);
+                            buffer.position(1);
+                            buffer.limit(2);
+                            BitSet buttons = BitSet.valueOf(buffer);
+                            buffer.clear();
+                            buffer.limit(2);
+                            socket.read(buffer);
+                            int y = buffer.getShort(0);
+                            final MouseState state = new MouseState();
+                            state.setX(x);
+                            state.setY(y);
+                            if (buttons.get(0)) {
+                                state.pressButton(MouseEvent.BUTTON_LEFT);
+                            }
+                            if (buttons.get(1)) {
+                                state.pressButton(MouseEvent.BUTTON_OTHER);
+                            }
+                            if (buttons.get(2)) {
+                                state.pressButton(MouseEvent.BUTTON_RIGHT);
+                            }
+                            Platform.runLater(new Runnable() {
+                                @Override
+                                public void run() {
+                                    MouseInput.getInstance().setState(state, false);
+                                }
+                            });
+                            break;
+                        }
+                        case 6: // ClientCutText
+                            buffer.clear();
+                            buffer.limit(4);
+                            socket.read(buffer);
+                            int textLength = buffer.getInt(0);
+                            for (int i = 0; i < textLength; i++) {
+                                buffer.clear();
+                                buffer.limit(1);
+                                socket.read(buffer);
+                            }
+                            break;
+                        default:
+                            System.err.format(
+                                    "Unknown message %d from client %s\n",
+                                    buffer.get(0), socket.getRemoteAddress());
+                    }
+                }
+            } catch (IOException e) {
+                removeClient(this, e);
+            }
+        }
+    }
+
+}