package org.jfxvnc.ui.control;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import org.jfxvnc.net.rfb.codec.decoder.ColourMapEvent;
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.rect.CopyImageRect;
import org.jfxvnc.net.rfb.render.rect.CursorImageRect;
import org.jfxvnc.net.rfb.render.rect.HextileImageRect;
import org.jfxvnc.net.rfb.render.rect.ImageRect;
import org.jfxvnc.net.rfb.render.rect.RawImageRect;
import org.jfxvnc.ui.CutTextEventHandler;
import org.jfxvnc.ui.KeyButtonEventHandler;
import org.jfxvnc.ui.PointerEventHandler;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Dimension2D;
import javafx.scene.Cursor;
import javafx.scene.ImageCursor;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritableImage;
public class VncImageView extends ImageView implements BiConsumer<ServerDecoderEvent, ImageRect> {
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(VncImageView.class);
private WritableImage vncImage;
private PointerEventHandler pointerHandler;
private CutTextEventHandler cutTextHandler;
private KeyButtonEventHandler keyHandler;
private ImageCursor remoteCursor;
private boolean useClientCursor = false;
private final PixelFormat<ByteBuffer> DEFAULT_PIXELFORMAT = PixelFormat.getByteRgbInstance();
private AtomicReference<PixelFormat<ByteBuffer>> pixelFormat = new AtomicReference<>(DEFAULT_PIXELFORMAT);
private SimpleDoubleProperty zoomLevel;
public VncImageView() {
setPreserveRatio(true);
registerListener();
}
public void registerListener() {
setOnMouseEntered(event -> {
if (!isDisabled()) {
requestFocus();
setCursor(remoteCursor != null ? remoteCursor : Cursor.DEFAULT);
}
});
setOnMouseExited(event -> {
if (!isDisabled()) {
setCursor(Cursor.DEFAULT);
}
});
zoomLevelProperty().addListener(l -> {
if (getImage() != null) {
setFitHeight(getImage().getHeight() * zoomLevelProperty().get());
}
});
}
public void setPixelFormat(ColourMapEvent event) {
int[] colors = new int[event.getNumberOfColor()];
int r, g, b;
for (int i = event.getFirstColor(); i < colors.length; i++) {
r = event.getColors().readUnsignedShort();
g = event.getColors().readUnsignedShort();
b = event.getColors().readUnsignedShort();
colors[i] = (0xff << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | (b >> 8);
}
pixelFormat.set(PixelFormat.createByteIndexedInstance(colors));
}
@Override
public void accept(ServerDecoderEvent event, ImageRect rect) {
if (event instanceof ConnectInfoEvent){
Platform.runLater(() -> setConnectInfoEvent((ConnectInfoEvent) event));
}else if (event instanceof ColourMapEvent){
Platform.runLater(() -> setPixelFormat((ColourMapEvent) event));
}
if (rect != null){
Platform.runLater(() -> render(rect));
}
}
private void render(ImageRect rect) {
try {
if (vncImage == null) {
logger.error("canvas image has not been initialized");
return;
}
switch (rect.getEncoding()) {
case HEXTILE:
HextileImageRect hextileRect = (HextileImageRect) rect;
//PixelWriter writer = vncImage.getPixelWriter();
for (RawImageRect rawRect : hextileRect.getRects()){
vncImage.getPixelWriter().setPixels(rawRect.getX(), rawRect.getY(), rawRect.getWidth(), rawRect.getHeight(), pixelFormat.get(),
rawRect.getPixels().nioBuffer(), rawRect.getScanlineStride());
}
break;
case RAW:
case ZLIB:
RawImageRect rawRect = (RawImageRect) rect;
vncImage.getPixelWriter().setPixels(rawRect.getX(), rawRect.getY(), rawRect.getWidth(), rawRect.getHeight(), pixelFormat.get(),
rawRect.getPixels().nioBuffer(), rawRect.getScanlineStride());
break;
case COPY_RECT:
CopyImageRect copyImageRect = (CopyImageRect) rect;
PixelReader reader = vncImage.getPixelReader();
WritableImage copyRect = new WritableImage(copyImageRect.getWidth(), copyImageRect.getHeight());
copyRect.getPixelWriter().setPixels(0, 0, copyImageRect.getWidth(), copyImageRect.getHeight(), reader, copyImageRect.getSrcX(),
copyImageRect.getSrcY());
vncImage.getPixelWriter().setPixels(copyImageRect.getX(), copyImageRect.getY(), copyImageRect.getWidth(), copyImageRect.getHeight(),
copyRect.getPixelReader(), 0, 0);
break;
case CURSOR:
if (!useClientCursor) {
logger.warn("ignore cursor encoding");
return;
}
final CursorImageRect cRect = (CursorImageRect) rect;
if (cRect.getHeight() < 2 && cRect.getWidth() < 2) {
setCursor(Cursor.NONE);
return;
}
Dimension2D dim = ImageCursor.getBestSize(cRect.getWidth(), cRect.getHeight());
WritableImage cImage = new WritableImage((int) dim.getWidth(), (int) dim.getHeight());
cImage.getPixelWriter().setPixels(0, 0, (int) Math.min(dim.getWidth(), cRect.getWidth()), (int) Math.min(dim.getHeight(), cRect.getHeight()),
PixelFormat.getIntArgbInstance(), cRect.getPixels().nioBuffer().asIntBuffer(), cRect.getWidth());
remoteCursor = new ImageCursor(cImage, cRect.getHotspotX(), cRect.getHotspotY());
setCursor(remoteCursor);
break;
case DESKTOP_SIZE:
logger.debug("resize image: {}", rect);
vncImage = new WritableImage(rect.getWidth(), rect.getHeight());
setImage(vncImage);
break;
default:
logger.error("not supported encoding rect: {}", rect);
break;
}
} catch (Exception e) {
logger.error("rect: " + String.valueOf(rect), e);
} finally {
rect.release();
}
}
public void registerInputEventListener(InputEventListener listener) {
Objects.requireNonNull(listener, "input listener must not be null");
if (pointerHandler == null) {
pointerHandler = new PointerEventHandler();
pointerHandler.register(this);
pointerHandler.registerZoomLevel(zoomLevelProperty());
pointerHandler.enabledProperty().bind(disabledProperty().not());
}
pointerHandler.setInputEventListener(listener);
if (keyHandler == null) {
keyHandler = new KeyButtonEventHandler();
keyHandler.register(getScene());
keyHandler.enabledProperty().bind(disabledProperty().not());
}
keyHandler.setInputEventListener(listener);
if (cutTextHandler == null) {
cutTextHandler = new CutTextEventHandler();
cutTextHandler.enabledProperty().bind(disabledProperty().not());
}
cutTextHandler.setInputEventListener(listener);
}
public void unregisterInputEventListener() {
if (pointerHandler != null) {
pointerHandler.unregister(this);
pointerHandler = null;
}
if (keyHandler != null) {
keyHandler.unregister(getScene());
keyHandler = null;
}
if (cutTextHandler != null) {
cutTextHandler.setInputEventListener(null);
cutTextHandler = null;
}
}
public DoubleProperty zoomLevelProperty() {
if (zoomLevel == null) {
zoomLevel = new SimpleDoubleProperty(1.0);
}
return zoomLevel;
}
public boolean isUseClientCursor() {
return useClientCursor;
}
public void setUseClientCursor(boolean flag) {
this.useClientCursor = flag;
if (!useClientCursor) {
setCursor(Cursor.DEFAULT);
}
}
public boolean addClipboardText(String text) {
if (cutTextHandler != null) {
cutTextHandler.addClipboardText(text);
return true;
}
return false;
}
public void setConnectInfoEvent(ConnectInfoEvent e) {
setImage(vncImage = new WritableImage(e.getFrameWidth(), e.getFrameHeight()));
setFitHeight(getImage().getHeight() * zoomLevelProperty().get());
pixelFormat.set(DEFAULT_PIXELFORMAT);
}
}