Package vncclient.vnc

Source Code of vncclient.vnc.VncMessageHandler

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
package vncclient.vnc;

import streamer.BaseElement;
import streamer.ByteBuffer;
import streamer.Element;
import streamer.Link;
import streamer.Pipeline;
import streamer.PipelineImpl;
import streamer.debug.MockSink;
import streamer.debug.MockSource;
import common.BitmapOrder;
import common.BitmapRectangle;
import common.CopyRectOrder;
import common.ScreenDescription;
import common.adapter.AwtClipboardAdapter;

public class VncMessageHandler extends BaseElement {
    protected ScreenDescription screen = null;

    // Pad names
    public static final String SERVER_BELL_ADAPTER_PAD = "bell";
    public static final String SERVER_CLIPBOARD_ADAPTER_PAD = "clipboard";
    public static final String PIXEL_ADAPTER_PAD = "pixels";
    public static final String FRAME_BUFFER_UPDATE_REQUEST_ADAPTER_PAD = "fbur";

    // Keys for metadata
    public static final String TARGET_X = "x";
    public static final String TARGET_Y = "y";
    public static final String WIDTH = "width";
    public static final String HEIGHT = "height";
    public static final String SOURCE_X = "srcX";
    public static final String SOURCE_Y = "srcY";
    public static final String PIXEL_FORMAT = "pixel_format";

    private static final String NUM_OF_PROCESSED_RECTANGLES = "rects";
    private static final String SAVED_CURSOR_POSITION = "cursor";

    // Pixel format: RGB888 LE 32
    public static final String RGB888LE32 = "RGB888LE32";

    public VncMessageHandler(String id, ScreenDescription screen) {
        super(id);
        this.screen = screen;
        declarePads();
    }

    private void declarePads() {
        outputPads.put(SERVER_BELL_ADAPTER_PAD, null);
        outputPads.put(SERVER_BELL_ADAPTER_PAD, null);
        outputPads.put(SERVER_CLIPBOARD_ADAPTER_PAD, null);
        outputPads.put(PIXEL_ADAPTER_PAD, null);
        outputPads.put(FRAME_BUFFER_UPDATE_REQUEST_ADAPTER_PAD, null);

        inputPads.put("stdin", null);
    }

    @Override
    public void handleData(ByteBuffer buf, Link link) {
        if (buf == null)
            return;

        try {
            if (verbose)
                System.out.println("[" + this + "] INFO: Data received: " + buf + ".");

            if (!cap(buf, 1, UNLIMITED, link, false))
                return;

            // Read server message type
            int messageType = buf.readUnsignedByte();

            // Invoke packet handler by packet type.
            switch (messageType) {

            case RfbConstants.SERVER_FRAMEBUFFER_UPDATE: {
                // Handle frame buffer update
                if (!handleFBU(buf, link))
                    return;

                // Frame buffer update is received and fully processed, send request for
                // another frame buffer update to server.
                sendFBUR();

                break;
            }

            case RfbConstants.SERVER_BELL: {
                if (!handleBell(buf, link))
                    return;
                break;
            }

            case RfbConstants.SERVER_CUT_TEXT: {
                if (!handleClipboard(buf, link))
                    return;
                break;
            }

            default:
                // TODO: allow to extend functionality
                throw new RuntimeException("Unknown server packet type: " + messageType + ".");
            }

            // Cut tail, if any
            cap(buf, 0, 0, link, true);
        } finally {

            // Return processed buffer back to pool
            buf.unref();
        }
    }

    private boolean handleClipboard(ByteBuffer buf, Link link) {
        if (!cap(buf, 3 + 4, UNLIMITED, link, true))
            return false;

        // Skip padding
        buf.skipBytes(3);

        // Read text length
        int length = buf.readSignedInt();

        // We need full string to parse it
        if (!cap(buf, length, UNLIMITED, link, true))
            return false;

        String content = buf.readString(length, RfbConstants.US_ASCII_CHARSET);

        // Send content in metadata
        ByteBuffer outBuf = new ByteBuffer(0);
        outBuf.putMetadata(AwtClipboardAdapter.CLIPBOARD_CONTENT, content);

        pushDataToPad(SERVER_CLIPBOARD_ADAPTER_PAD, outBuf);

        return true;
    }

    private boolean handleBell(ByteBuffer buf, Link link) {
        // Send empty packet to bell adapter to produce bell
        pushDataToPad(SERVER_BELL_ADAPTER_PAD, new ByteBuffer(0));

        return true;
    }

    // FIXME: this method is too complex
    private boolean handleFBU(ByteBuffer buf, Link link) {

        // We need at least 3 bytes here, 1 - padding, 2 - number of rectangles
        if (!cap(buf, 3, UNLIMITED, link, true))
            return false;

        buf.skipBytes(1);// Skip padding

        // Read number of rectangles
        int numberOfRectangles = buf.readUnsignedShort();

        if (verbose)
            System.out.println("[" + this + "] INFO: Frame buffer update. Number of rectangles: " + numberOfRectangles + ".");

        // Each rectangle must have header at least, header length is 12 bytes.
        if (!cap(buf, 12 * numberOfRectangles, UNLIMITED, link, true))
            return false;

        // For all rectangles

        // Restore saved point, to avoid flickering and performance problems when
        // frame buffer update is split between few incoming packets.
        int numberOfProcessedRectangles = (buf.getMetadata(NUM_OF_PROCESSED_RECTANGLES) != null) ? (Integer)buf.getMetadata(NUM_OF_PROCESSED_RECTANGLES) : 0;
        if (buf.getMetadata(SAVED_CURSOR_POSITION) != null)
            buf.cursor = (Integer)buf.getMetadata(SAVED_CURSOR_POSITION);

        if (verbose && numberOfProcessedRectangles > 0)
            System.out.println("[" + this + "] INFO: Restarting from saved point. Number of already processed rectangles: " + numberOfRectangles + ", cursor: "
                    + buf.cursor + ".");

        // For all new rectangles
        for (int i = numberOfProcessedRectangles; i < numberOfRectangles; i++) {

            // We need coordinates of rectangle (2x4 bytes) and encoding type (4
            // bytes)
            if (!cap(buf, 12, UNLIMITED, link, true))
                return false;

            // Read coordinates of rectangle
            int x = buf.readUnsignedShort();
            int y = buf.readUnsignedShort();
            int width = buf.readUnsignedShort();
            int height = buf.readUnsignedShort();

            // Read rectangle encoding
            int encodingType = buf.readSignedInt();

            // Process rectangle
            switch (encodingType) {

            case RfbConstants.ENCODING_RAW: {
                if (!handleRawRectangle(buf, link, x, y, width, height))
                    return false;
                break;
            }

            case RfbConstants.ENCODING_COPY_RECT: {
                if (!handleCopyRect(buf, link, x, y, width, height))
                    return false;
                break;
            }

            case RfbConstants.ENCODING_DESKTOP_SIZE: {
                if (!handleScreenSizeChangeRect(buf, link, x, y, width, height))
                    return false;
                break;
            }

            default:
                // TODO: allow to extend functionality
                throw new RuntimeException("Unsupported ecnoding: " + encodingType + ".");
            }

            // Update information about processed rectangles to avoid handling of same
            // rectangle multiple times.
            // TODO: push back partial rectangle only instead
            buf.putMetadata(NUM_OF_PROCESSED_RECTANGLES, ++numberOfProcessedRectangles);
            buf.putMetadata(SAVED_CURSOR_POSITION, buf.cursor);
        }

        return true;
    }

    private boolean handleScreenSizeChangeRect(ByteBuffer buf, Link link, int x, int y, int width, int height) {
        // Remote screen size is changed
        if (verbose)
            System.out.println("[" + this + "] INFO: Screen size rect. Width: " + width + ", height: " + height + ".");

        screen.setFramebufferSize(width, height);

        return true;
    }

    private boolean handleCopyRect(ByteBuffer buf, Link link, int x, int y, int width, int height) {
        // Copy rectangle from one part of screen to another.
        // Areas may overlap. Antialiasing may cause visible artifacts.

        // We need 4 bytes with coordinates of source rectangle
        if (!cap(buf, 4, UNLIMITED, link, true))
            return false;

        CopyRectOrder order = new CopyRectOrder();

        order.srcX = buf.readUnsignedShort();
        order.srcY = buf.readUnsignedShort();
        order.x = x;
        order.y = y;
        order.width = width;
        order.height = height;

        if (verbose)
            System.out.println("[" + this + "] INFO: Copy rect. X: " + x + ", y: " + y + ", width: " + width + ", height: " + height + ", srcX: " + order.srcX
                    + ", srcY: " + order.srcY + ".");

        pushDataToPad(PIXEL_ADAPTER_PAD, new ByteBuffer(order));

        return true;
    }

    private boolean handleRawRectangle(ByteBuffer buf, Link link, int x, int y, int width, int height) {
        // Raw rectangle is just array of pixels to draw on screen.
        int rectDataLength = width * height * screen.getBytesPerPixel();

        // We need at least rectDataLength bytes. Extra bytes may contain other
        // rectangles.
        if (!cap(buf, rectDataLength, UNLIMITED, link, true))
            return false;

        if (verbose)
            System.out.println("[" + this + "] INFO: Raw rect. X: " + x + ", y: " + y + ", width: " + width + ", height: " + height + ", data length: "
                    + rectDataLength + ".");

        BitmapRectangle rectangle = new BitmapRectangle();
        rectangle.x = x;
        rectangle.y = y;
        rectangle.width = width;
        rectangle.height = height;
        rectangle.bufferWidth = width;
        rectangle.bufferHeight = height;
        rectangle.bitmapDataStream = buf.readBytes(rectDataLength);
        rectangle.colorDepth = screen.getColorDeph();

        BitmapOrder order = new BitmapOrder();
        order.rectangles = new BitmapRectangle[] {rectangle};

        pushDataToPad(PIXEL_ADAPTER_PAD, new ByteBuffer(order));
        return true;
    }

    @Override
    public void onStart() {
        // Send Frame Buffer Update request
        sendFBUR();
    }

    private void sendFBUR() {
        ByteBuffer buf = new ByteBuffer(0);
        buf.putMetadata("incremental", true);
        pushDataToPad(FRAME_BUFFER_UPDATE_REQUEST_ADAPTER_PAD, buf);
    }

    @Override
    public String toString() {
        return "VNCMessageHandler(" + id + ")";
    }

    /**
     * Example.
     */
    public static void main(String[] args) {

        // System.setProperty("streamer.Link.debug", "true");
        System.setProperty("streamer.Element.debug", "true");
        // System.setProperty("streamer.Pipeline.debug", "true");

        Element source = new MockSource("source") {
            {
                // Split messages at random boundaries to check "pushback" logic
                bufs = ByteBuffer.convertByteArraysToByteBuffers(new byte[] {
                        // Message type: server bell
                        RfbConstants.SERVER_BELL,

                        // Message type: clipboard text
                        RfbConstants.SERVER_CUT_TEXT,
                        // Padding
                        0, 0, 0,
                        // Length (test)
                        0, 0, 0, 4,

                }, new byte[] {
                        // Clipboard text
                        't', 'e', 's', 't',

                        // Message type: frame buffer update
                        RfbConstants.SERVER_FRAMEBUFFER_UPDATE,
                        // Padding
                        0,
                        // Number of rectangles
                        0, 3,},

                        new byte[] {

                        // x, y, width, height: 0x0@4x4
                        0, 0, 0, 0, 0, 4, 0, 4,
                        // Encoding: desktop size
                        (byte)((RfbConstants.ENCODING_DESKTOP_SIZE >> 24) & 0xff), (byte)((RfbConstants.ENCODING_DESKTOP_SIZE >> 16) & 0xff),
                        (byte)((RfbConstants.ENCODING_DESKTOP_SIZE >> 8) & 0xff), (byte)((RfbConstants.ENCODING_DESKTOP_SIZE >> 0) & 0xff),},

                        new byte[] {

                        // x, y, width, height: 0x0@4x4
                        0, 0, 0, 0, 0, 4, 0, 4,
                        // Encoding: raw rect
                        (byte)((RfbConstants.ENCODING_RAW >> 24) & 0xff), (byte)((RfbConstants.ENCODING_RAW >> 16) & 0xff),
                        (byte)((RfbConstants.ENCODING_RAW >> 8) & 0xff), (byte)((RfbConstants.ENCODING_RAW >> 0) & 0xff),
                        // Raw pixel data 4x4x1 bpp
                        1, 2, 3, 4, 5, 6, 7, 8, 9, 10,}, new byte[] {11, 12, 13, 14, 15, 16,

                        // x, y, width, height: 0x0@2x2
                        0, 0, 0, 0, 0, 2, 0, 2,
                        // Encoding: copy rect
                        (byte)((RfbConstants.ENCODING_COPY_RECT >> 24) & 0xff), (byte)((RfbConstants.ENCODING_COPY_RECT >> 16) & 0xff),
                        (byte)((RfbConstants.ENCODING_COPY_RECT >> 8) & 0xff), (byte)((RfbConstants.ENCODING_COPY_RECT >> 0) & 0xff),
                        // srcX, srcY: 2x2
                        0, 2, 0, 2,});
            }
        };

        ScreenDescription screen = new ScreenDescription() {
            {
                bytesPerPixel = 1;
            }
        };

        final Element handler = new VncMessageHandler("handler", screen);

        ByteBuffer[] emptyBuf = ByteBuffer.convertByteArraysToByteBuffers(new byte[] {});
        Element fburSink = new MockSink("fbur", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {}, new byte[] {}));
        Element bellSink = new MockSink("bell", emptyBuf);
        Element clipboardSink = new MockSink("clipboard", emptyBuf);
        Element desktopSizeChangeSink = new MockSink("desktop_size", emptyBuf);
        Element pixelsSink = new MockSink("pixels",
                ByteBuffer.convertByteArraysToByteBuffers(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,}));
        Element copyRectSink = new MockSink("copy_rect", emptyBuf);

        Pipeline pipeline = new PipelineImpl("test");
        pipeline.addAndLink(source, handler);
        pipeline.add(fburSink, bellSink, clipboardSink, desktopSizeChangeSink, pixelsSink, copyRectSink);

        pipeline.link("handler >" + FRAME_BUFFER_UPDATE_REQUEST_ADAPTER_PAD, "fbur");
        pipeline.link("handler >" + SERVER_BELL_ADAPTER_PAD, "bell");
        pipeline.link("handler >" + SERVER_CLIPBOARD_ADAPTER_PAD, "clipboard");
        pipeline.link("handler >" + PIXEL_ADAPTER_PAD, "pixels");

        pipeline.runMainLoop("source", STDOUT, false, false);

    }

}
TOP

Related Classes of vncclient.vnc.VncMessageHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.