/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2011-2017 Brian P. Hinz
* Copyright (C) 2017 Pierre Ossman for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
//
// CMsgReader - class for reading RFB messages on the client side
// (i.e. messages from server to client).
//
package com.tigervnc.rfb;
import java.awt.image.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import com.tigervnc.rdr.*;
public class CMsgReader {
protected CMsgReader(CMsgHandler handler_, InStream is_)
{
imageBufIdealSize = 0;
handler = handler_;
is = is_;
imageBuf = null;
imageBufSize = 0;
nUpdateRectsLeft = 0;
}
public void readServerInit()
{
int width = is.readU16();
int height = is.readU16();
handler.setDesktopSize(width, height);
PixelFormat pf = new PixelFormat();
pf.read(is);
handler.setPixelFormat(pf);
String name = is.readString();
handler.setName(name);
handler.serverInit();
}
public void readMsg()
{
if (nUpdateRectsLeft == 0) {
int type = is.readU8();
switch (type) {
case MsgTypes.msgTypeSetColourMapEntries:
readSetColourMapEntries();
break;
case MsgTypes.msgTypeBell:
readBell();
break;
case MsgTypes.msgTypeServerCutText:
readServerCutText();
break;
case MsgTypes.msgTypeFramebufferUpdate:
readFramebufferUpdate();
break;
case MsgTypes.msgTypeServerFence:
readFence();
break;
case MsgTypes.msgTypeEndOfContinuousUpdates:
readEndOfContinuousUpdates();
break;
default:
throw new Exception("unknown message type");
}
} else {
int x = is.readU16();
int y = is.readU16();
int w = is.readU16();
int h = is.readU16();
int encoding = is.readS32();
switch (encoding) {
case Encodings.pseudoEncodingLastRect:
nUpdateRectsLeft = 1; // this rectangle is the last one
break;
case Encodings.pseudoEncodingXCursor:
readSetXCursor(w, h, new Point(x,y));
break;
case Encodings.pseudoEncodingCursor:
readSetCursor(w, h, new Point(x,y));
break;
case Encodings.pseudoEncodingCursorWithAlpha:
readSetCursorWithAlpha(w, h, new Point(x,y));
break;
case Encodings.pseudoEncodingDesktopName:
readSetDesktopName(x, y, w, h);
break;
case Encodings.pseudoEncodingDesktopSize:
handler.setDesktopSize(w, h);
break;
case Encodings.pseudoEncodingExtendedDesktopSize:
readExtendedDesktopSize(x, y, w, h);
break;
case Encodings.pseudoEncodingClientRedirect:
nUpdateRectsLeft = 0;
readClientRedirect(x, y, w, h);
return;
default:
readRect(new Rect(x, y, x+w, y+h), encoding);
break;
};
nUpdateRectsLeft--;
if (nUpdateRectsLeft == 0)
handler.framebufferUpdateEnd();
}
}
protected void readSetColourMapEntries()
{
is.skip(1);
int firstColour = is.readU16();
int nColours = is.readU16();
int[] rgbs = new int[nColours * 3];
for (int i = 0; i < nColours * 3; i++)
rgbs[i] = is.readU16();
handler.setColourMapEntries(firstColour, nColours, rgbs);
}
protected void readBell()
{
handler.bell();
}
protected void readServerCutText()
{
is.skip(3);
int len = is.readU32();
if (len > 256*1024) {
is.skip(len);
vlog.error("cut text too long ("+len+" bytes) - ignoring");
return;
}
byte[] buf = new byte[len];
is.readBytes(buf, 0, len);
Charset latin1 = Charset.forName("ISO-8859-1");
CharBuffer chars = latin1.decode(ByteBuffer.wrap(buf));
handler.serverCutText(chars.toString(), len);
}
protected void readFence()
{
int flags;
int len;
byte[] data = new byte[64];
is.skip(3);
flags = is.readU32();
len = is.readU8();
if (len > data.length) {
System.out.println("Ignoring fence with too large payload\n");
is.skip(len);
return;
}
is.readBytes(data, 0, len);
handler.fence(flags, len, data);
}
protected void readEndOfContinuousUpdates()
{
handler.endOfContinuousUpdates();
}
protected void readFramebufferUpdate()
{
is.skip(1);
nUpdateRectsLeft = is.readU16();
handler.framebufferUpdateStart();
}
protected void readRect(Rect r, int encoding)
{
if ((r.br.x > handler.cp.width) || (r.br.y > handler.cp.height)) {
vlog.error("Rect too big: "+r.width()+"x"+r.height()+" at "+
r.tl.x+","+r.tl.y+" exceeds "+handler.cp.width+"x"+
handler.cp.height);
throw new Exception("Rect too big");
}
if (r.is_empty())
vlog.error("Ignoring zero size rect");
handler.dataRect(r, encoding);
}
protected void readSetXCursor(int width, int height, Point hotspot)
{
byte pr, pg, pb;
byte sr, sg, sb;
int data_len = ((width+7)/8) * height;
int mask_len = ((width+7)/8) * height;
ByteBuffer data = ByteBuffer.allocate(data_len);
ByteBuffer mask = ByteBuffer.allocate(mask_len);
int x, y;
byte[] buf = new byte[width*height*4];
ByteBuffer out;
if (width * height == 0)
return;
pr = (byte)is.readU8();
pg = (byte)is.readU8();
pb = (byte)is.readU8();
sr = (byte)is.readU8();
sg = (byte)is.readU8();
sb = (byte)is.readU8();
is.readBytes(data, data_len);
is.readBytes(mask, mask_len);
int maskBytesPerRow = (width+7)/8;
out = ByteBuffer.wrap(buf);
for (y = 0;y < height;y++) {
for (x = 0;x < width;x++) {
int byte_ = y * maskBytesPerRow + x / 8;
int bit = 7 - x % 8;
if ((data.get(byte_) & (1 << bit)) > 0) {
out.put(out.position() + 1, pr);
out.put(out.position() + 2, pg);
out.put(out.position() + 3, pb);
} else {
out.put(out.position() + 1, sr);
out.put(out.position() + 2, sg);
out.put(out.position() + 3, sb);
}
if ((mask.get(byte_) & (1 << bit)) > 0)
out.put(out.position() + 0, (byte)255);
else
out.put(out.position() + 0, (byte)0);
out.position(out.position() + 4);
}
}
handler.setCursor(width, height, hotspot, buf);
}
protected void readSetCursor(int width, int height, Point hotspot)
{
int data_len = width * height * (handler.cp.pf().bpp/8);
int mask_len = ((width+7)/8) * height;
ByteBuffer data = ByteBuffer.allocate(data_len);
ByteBuffer mask = ByteBuffer.allocate(mask_len);
int x, y;
byte[] buf = new byte[width*height*4];
ByteBuffer in;
ByteBuffer out;
is.readBytes(data, data_len);
is.readBytes(mask, mask_len);
int maskBytesPerRow = (width+7)/8;
in = (ByteBuffer)data.duplicate().mark();
out = (ByteBuffer)ByteBuffer.wrap(buf).mark();
for (y = 0;y < height;y++) {
for (x = 0;x < width;x++) {
int byte_ = y * maskBytesPerRow + x / 8;
int bit = 7 - x % 8;
// NOTE: BufferedImage needs ARGB, rather than RGBA
if ((mask.get(byte_) & (1 << bit)) != 0)
out.put((byte)255);
else
out.put((byte)0);
handler.cp.pf().rgbFromBuffer(out, in.duplicate(), 1);
in.position(in.position() + handler.cp.pf().bpp/8);
out.position(out.reset().position() + 4).mark();
}
}
handler.setCursor(width, height, hotspot, buf);
}
protected void readSetCursorWithAlpha(int width, int height, Point hotspot)
{
int encoding;
PixelFormat rgbaPF =
new PixelFormat(32, 32, false, true, 255, 255, 255, 16, 8, 0);
ManagedPixelBuffer pb =
new ManagedPixelBuffer(rgbaPF, width, height);
PixelFormat origPF;
DataBufferInt buf;
encoding = is.readS32();
origPF = handler.cp.pf();
handler.cp.setPF(rgbaPF);
handler.readAndDecodeRect(pb.getRect(), encoding, pb);
handler.cp.setPF(origPF);
if (pb.getRect().area() == 0)
return;
// ARGB with pre-multiplied alpha works best for BufferedImage
buf = (DataBufferInt)pb.getBufferRW(pb.getRect()).getDataBuffer();
ByteBuffer bbuf =
ByteBuffer.allocate(pb.area()*4).order(rgbaPF.getByteOrder());
bbuf.asIntBuffer().put(buf.getData()).flip().mark();
for (int i = 0;i < pb.area();i++) {
byte alpha = bbuf.get(bbuf.position()+3);
bbuf.put(i*4+3, (byte)(bbuf.get(i*4+2)));
bbuf.put(i*4+2, (byte)(bbuf.get(i*4+1)));
bbuf.put(i*4+1, (byte)(bbuf.get(i*4+0)));
bbuf.put(i*4+0, (byte)alpha);
bbuf.position(bbuf.position() + 4);
}
handler.setCursor(width, height, hotspot,
bbuf.array());
}
protected void readSetDesktopName(int x, int y, int w, int h)
{
String name = is.readString();
if (x != 0 || y != 0 || w != 0 || h != 0) {
vlog.error("Ignoring DesktopName rect with non-zero position/size");
} else {
handler.setName(name);
}
}
protected void readExtendedDesktopSize(int x, int y, int w, int h)
{
int screens, i;
int id, flags;
int sx, sy, sw, sh;
ScreenSet layout = new ScreenSet();
screens = is.readU8();
is.skip(3);
for (i = 0;i < screens;i++) {
id = is.readU32();
sx = is.readU16();
sy = is.readU16();
sw = is.readU16();
sh = is.readU16();
flags = is.readU32();
layout.add_screen(new Screen(id, sx, sy, sw, sh, flags));
}
handler.setExtendedDesktopSize(x, y, w, h, layout);
}
protected void readClientRedirect(int x, int y, int w, int h)
{
int port = is.readU16();
String host = is.readString();
String x509subject = is.readString();
if (x != 0 || y != 0 || w != 0 || h != 0)
vlog.error("Ignoring ClientRedirect rect with non-zero position/size");
else
handler.clientRedirect(port, host, x509subject);
}
public int[] getImageBuf(int required) { return getImageBuf(required, 0, 0); }
public int[] getImageBuf(int required, int requested, int nPixels)
{
int requiredBytes = required;
int requestedBytes = requested;
int size = requestedBytes;
if (size > imageBufIdealSize) size = imageBufIdealSize;
if (size < requiredBytes)
size = requiredBytes;
if (imageBufSize < size) {
imageBufSize = size;
imageBuf = new int[imageBufSize];
}
if (nPixels != 0)
nPixels = imageBufSize / (handler.cp.pf().bpp / 8);
return imageBuf;
}
public InStream getInStream() { return is; }
public int imageBufIdealSize;
protected CMsgHandler handler;
protected InStream is;
protected int nUpdateRectsLeft;
protected int[] imageBuf;
protected int imageBufSize;
static LogWriter vlog = new LogWriter("CMsgReader");
}