org.jfxvnc.ui.service.VncRenderService.java Source code

Java tutorial

Introduction

Here is the source code for org.jfxvnc.ui.service.VncRenderService.java

Source

/*******************************************************************************
 * Copyright (c) 2016 comtel inc.
 *
 * Licensed 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 org.jfxvnc.ui.service;

import java.util.concurrent.TimeUnit;

import javax.annotation.PreDestroy;
import javax.inject.Inject;

import org.jfxvnc.net.rfb.codec.ProtocolInitializer;
import org.jfxvnc.net.rfb.codec.ProtocolState;
import org.jfxvnc.net.rfb.codec.decoder.BellEvent;
import org.jfxvnc.net.rfb.codec.decoder.ColourMapEntriesEvent;
import org.jfxvnc.net.rfb.codec.decoder.ServerCutTextEvent;
import org.jfxvnc.net.rfb.codec.decoder.ServerDecoderEvent;
import org.jfxvnc.net.rfb.codec.encoder.InputEventListener;
import org.jfxvnc.net.rfb.render.ConnectInfoEvent;
import org.jfxvnc.net.rfb.render.ProtocolConfiguration;
import org.jfxvnc.net.rfb.render.RenderCallback;
import org.jfxvnc.net.rfb.render.RenderProtocol;
import org.jfxvnc.net.rfb.render.rect.ImageRect;
import org.slf4j.LoggerFactory;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;

public class VncRenderService extends Service<Boolean> implements RenderProtocol {

    private final static org.slf4j.Logger logger = LoggerFactory.getLogger(VncRenderService.class);

    @Inject
    ProtocolConfiguration config;

    private static final int CONNECT_PORT = 5900;
    private static final int LISTENING_PORT = 5500;

    private final BooleanProperty listeningModeProperty = new SimpleBooleanProperty(false);
    private final IntegerProperty listeningPortProperty = new SimpleIntegerProperty(LISTENING_PORT);

    private final ReadOnlyBooleanWrapper connectProperty = new ReadOnlyBooleanWrapper(false);
    private final ReadOnlyBooleanWrapper onlineProperty = new ReadOnlyBooleanWrapper(false);

    private final ReadOnlyBooleanWrapper bellProperty = new ReadOnlyBooleanWrapper(false);
    private final ReadOnlyStringWrapper serverCutTextProperty = new ReadOnlyStringWrapper();

    private final ReadOnlyObjectWrapper<ConnectInfoEvent> connectInfoProperty = new ReadOnlyObjectWrapper<>();
    private final ReadOnlyObjectWrapper<ProtocolState> protocolStateProperty = new ReadOnlyObjectWrapper<>(
            ProtocolState.CLOSED);
    private final ReadOnlyObjectWrapper<InputEventListener> inputProperty = new ReadOnlyObjectWrapper<>();
    private final ReadOnlyObjectWrapper<ImageRect> imageProperty = new ReadOnlyObjectWrapper<>();
    private final ReadOnlyObjectWrapper<ColourMapEntriesEvent> colourMapEntriesEventProperty = new ReadOnlyObjectWrapper<>();

    private final ReadOnlyObjectWrapper<Throwable> exceptionCaughtProperty = new ReadOnlyObjectWrapper<>();

    private final double minZoomLevel = 0.2;
    private final double maxZoomLevel = 5.0;

    private final DoubleProperty zoomLevelProperty = new SimpleDoubleProperty(1);
    private final BooleanProperty fullSceenProperty = new SimpleBooleanProperty(false);
    private final BooleanProperty restartProperty = new SimpleBooleanProperty(false);

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    public VncRenderService() {

        protocolStateProperty.addListener((l) -> {
            if (protocolStateProperty.get() == ProtocolState.CLOSED) {
                connectProperty.set(false);
            }
        });
        connectProperty.addListener((l, a, b) -> {
            if (!b) {
                onlineProperty.set(false);
            }
        });

        zoomLevelProperty.addListener((l, a, b) -> {
            if (b.doubleValue() > maxZoomLevel) {
                zoomLevelProperty.set(maxZoomLevel);
            } else if (b.doubleValue() < minZoomLevel) {
                zoomLevelProperty.set(minZoomLevel);
            }
        });
    }

    public void validateConnection() throws Exception {
        logger.warn("not implemented yet");
    }

    private boolean connect() throws Exception {
        connectProperty.set(true);
        shutdown();
        workerGroup = new NioEventLoopGroup();

        String host = config.hostProperty().get();
        int port = config.portProperty().get() > 0 ? config.portProperty().get() : CONNECT_PORT;

        Bootstrap b = new Bootstrap();
        b.group(workerGroup);
        b.channel(NioSocketChannel.class);
        b.option(ChannelOption.SO_KEEPALIVE, true);
        b.option(ChannelOption.TCP_NODELAY, true);
        b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);

        b.handler(new ProtocolInitializer(VncRenderService.this, config));

        logger.info("try to connect to {}:{}", host, port);
        ChannelFuture f = b.connect(host, port);
        f.await(5000);

        connectProperty.set(f.isSuccess());
        logger.info("connection {}", connectProperty.get() ? "established" : "failed");
        if (f.isCancelled()) {
            logger.warn("connection aborted");
        } else if (!f.isSuccess()) {
            logger.error("connection failed", f.cause());
            exceptionCaughtProperty.set(f.cause() != null ? f.cause()
                    : new Exception("connection failed to host: " + host + ":" + port));
        }
        return connectProperty.get();
    }

    private void startListening() throws Exception {
        connectProperty.set(true);
        shutdown();
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100);

        b.childHandler(new ProtocolInitializer(VncRenderService.this, config));

        int port = listeningPortProperty.get() > 0 ? listeningPortProperty.get() : LISTENING_PORT;
        b.bind(port).addListener(l -> {
            logger.info("wait for incoming connection request on port: {}..", port);
            connectProperty.set(l.isSuccess());
        }).sync();

    }

    @PreDestroy
    @Override
    public boolean cancel() {
        Platform.runLater(() -> super.cancel());
        shutdown();
        connectProperty.set(false);
        return true;
    }

    private void shutdown() {
        if (workerGroup != null && !workerGroup.isTerminated()) {
            workerGroup.shutdownGracefully(2, 5, TimeUnit.SECONDS);
        }
        if (bossGroup != null && !bossGroup.isTerminated()) {
            bossGroup.shutdownGracefully(2, 5, TimeUnit.SECONDS);
        }
    }

    @Override
    protected Task<Boolean> createTask() {
        return new Task<Boolean>() {
            @Override
            protected Boolean call() throws Exception {
                if (listeningModeProperty.get()) {
                    startListening();
                    return true;
                }
                return connect();
            }
        };
    }

    @Override
    public void render(ImageRect rect, RenderCallback callback) {
        imageProperty.set(rect);
        callback.renderComplete();
    }

    @Override
    public void eventReceived(ServerDecoderEvent event) {
        logger.trace("event received: {}", event);
        if (event instanceof ConnectInfoEvent) {
            connectInfoProperty.set((ConnectInfoEvent) event);
            onlineProperty.set(true);
            return;
        }
        if (event instanceof BellEvent) {
            bellProperty.set(!bellProperty.get());
            return;
        }
        if (event instanceof ServerCutTextEvent) {
            serverCutTextProperty.set(((ServerCutTextEvent) event).getText());
            return;
        }
        if (event instanceof ColourMapEntriesEvent) {
            colourMapEntriesEventProperty.set((ColourMapEntriesEvent) event);
            return;
        }

        logger.warn("not handled event: {}", event);
    }

    @Override
    public void exceptionCaught(Throwable t) {
        exceptionCaughtProperty.set(t);
    }

    @Override
    public void stateChanged(ProtocolState state) {
        protocolStateProperty.set(state);
        if (state == ProtocolState.CLOSED) {
            cancel();
        }
    }

    @Override
    public void registerInputEventListener(InputEventListener listener) {
        inputProperty.set(listener);
    }

    public ReadOnlyObjectProperty<ConnectInfoEvent> connectInfoProperty() {
        return connectInfoProperty.getReadOnlyProperty();
    }

    public ReadOnlyObjectProperty<ProtocolState> protocolStateProperty() {
        return protocolStateProperty;
    }

    public ReadOnlyObjectProperty<InputEventListener> inputProperty() {
        return inputProperty.getReadOnlyProperty();
    }

    public ReadOnlyObjectProperty<ImageRect> imageProperty() {
        return imageProperty.getReadOnlyProperty();
    }

    public ReadOnlyObjectProperty<ColourMapEntriesEvent> colourMapEntriesEventProperty() {
        return colourMapEntriesEventProperty.getReadOnlyProperty();
    }

    public ReadOnlyObjectProperty<Throwable> exceptionCaughtProperty() {
        return exceptionCaughtProperty.getReadOnlyProperty();
    }

    public ReadOnlyBooleanProperty connectProperty() {
        return connectProperty.getReadOnlyProperty();
    }

    public ReadOnlyBooleanProperty onlineProperty() {
        return onlineProperty.getReadOnlyProperty();
    }

    public ReadOnlyBooleanProperty bellProperty() {
        return bellProperty.getReadOnlyProperty();
    }

    public ReadOnlyStringProperty serverCutTextProperty() {
        return serverCutTextProperty.getReadOnlyProperty();
    }

    public DoubleProperty zoomLevelProperty() {
        return zoomLevelProperty;
    }

    public BooleanProperty fullSceenProperty() {
        return fullSceenProperty;
    }

    public BooleanProperty restartProperty() {
        return restartProperty;
    }

    public BooleanProperty listeningModeProperty() {
        return listeningModeProperty;
    }

    public IntegerProperty listeningPortProperty() {
        return listeningPortProperty;
    }
}