Files
2026-01-29 11:29:56 +08:00

532 lines
15 KiB
Java

/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
* Copyright (C) 2011-2019 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 {
static LogWriter vlog = new LogWriter("CMsgReader");
protected CMsgReader(CMsgHandler handler_, InStream is_)
{
imageBufIdealSize = 0;
handler = handler_;
is = is_;
nUpdateRectsLeft = 0;
imageBuf = null;
imageBufSize = 0;
}
public void readServerInit()
{
int width = is.readU16();
int height = is.readU16();
PixelFormat pf = new PixelFormat();
pf.read(is);
String name = is.readString();
handler.serverInit(width, height, pf, name);
}
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:
vlog.error("Unknown message type "+type);
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.pseudoEncodingVMwareCursor:
readSetVMwareCursor(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;
}
ByteBuffer buf = ByteBuffer.allocate(len);
is.readBytes(buf, len);
Charset latin1 = Charset.forName("ISO-8859-1");
CharBuffer chars = latin1.decode(buf.compact());
handler.serverCutText(chars.toString(), len);
}
protected void readFence()
{
int flags;
int len;
ByteBuffer data = ByteBuffer.allocate(64);
is.skip(3);
flags = is.readU32();
len = is.readU8();
if (len > data.capacity()) {
System.out.println("Ignoring fence with too large payload\n");
is.skip(len);
return;
}
is.readBytes(data, len);
handler.fence(flags, len, data.array());
}
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.server.width()) || (r.br.y > handler.server.height())) {
vlog.error("Rect too big: "+r.width()+"x"+r.height()+" at "+
r.tl.x+","+r.tl.y+" exceeds "+handler.server.width()+"x"+
handler.server.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;
// NOTE: BufferedImage needs ARGB, rather than RGBA
if ((mask.get(byte_) & (1 << bit)) > 0)
out.put(out.position(), (byte)255);
else
out.put(out.position(), (byte)0);
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);
}
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.server.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.wrap(data.array());
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;
// NOTE: BufferedImage needs ARGB, rather than RGBA
if ((mask.get(byte_) & (1 << bit)) != 0)
out.put((byte)255);
else
out.put((byte)0);
handler.server.pf().rgbFromBuffer(out.duplicate(), in.duplicate(), 1);
in.position(in.position() + handler.server.pf().bpp/8);
out.position(out.position() + 3);
}
}
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;
ByteBuffer buf =
ByteBuffer.allocate(pb.area()*4).order(rgbaPF.getByteOrder());;
encoding = is.readS32();
origPF = handler.server.pf();
handler.server.setPF(rgbaPF);
handler.readAndDecodeRect(pb.getRect(), encoding, pb);
handler.server.setPF(origPF);
// ARGB with pre-multiplied alpha works best for BufferedImage
if (pb.area() > 0) {
// Sometimes a zero width or height cursor is sent.
DataBuffer db = pb.getBuffer(pb.getRect()).getDataBuffer();
for (int i = 0;i < pb.area();i++)
buf.asIntBuffer().put(i, db.getElem(i));
}
for (int i = 0;i < pb.area();i++) {
byte alpha = buf.get(buf.position()+3);
buf.put(i*4+3, buf.get(i*4+2));
buf.put(i*4+2, buf.get(i*4+1));
buf.put(i*4+1, buf.get(i*4+0));
buf.put(i*4+0, alpha);
buf.position(buf.position() + 4);
}
handler.setCursor(width, height, hotspot, buf.array());
}
protected void readSetVMwareCursor(int width, int height, Point hotspot)
{
// VMware cursor sends RGBA, java BufferedImage needs ARGB
if (width > maxCursorSize || height > maxCursorSize)
throw new Exception("Too big cursor");
byte type;
type = (byte)is.readU8();
is.skip(1);
if (type == 0) {
int len = width * height * (handler.server.pf().bpp/8);
ByteBuffer andMask = ByteBuffer.allocate(len);
ByteBuffer xorMask = ByteBuffer.allocate(len);
ByteBuffer data = ByteBuffer.allocate(width*height*4);
ByteBuffer andIn;
ByteBuffer xorIn;
ByteBuffer out;
int Bpp;
is.readBytes(andMask, len);
is.readBytes(xorMask, len);
andIn = ByteBuffer.wrap(andMask.array());
xorIn = ByteBuffer.wrap(xorMask.array());
out = ByteBuffer.wrap(data.array());
Bpp = handler.server.pf().bpp/8;
for (int y = 0;y < height;y++) {
for (int x = 0;x < width;x++) {
int andPixel, xorPixel;
andPixel = handler.server.pf().pixelFromBuffer(andIn.duplicate());
xorPixel = handler.server.pf().pixelFromBuffer(xorIn.duplicate());
andIn.position(andIn.position() + Bpp);
xorIn.position(xorIn.position() + Bpp);
if (andPixel == 0) {
byte r, g, b;
// Opaque pixel
r = (byte)handler.server.pf().getColorModel().getRed(xorPixel);
g = (byte)handler.server.pf().getColorModel().getGreen(xorPixel);
b = (byte)handler.server.pf().getColorModel().getBlue(xorPixel);
out.put((byte)0xff);
out.put(r);
out.put(g);
out.put(b);
} else if (xorPixel == 0) {
// Fully transparent pixel
out.put((byte)0);
out.put((byte)0);
out.put((byte)0);
out.put((byte)0);
} else if (andPixel == xorPixel) {
// Inverted pixel
// We don't really support this, so just turn the pixel black
// FIXME: Do an outline like WinVNC does?
out.put((byte)0xff);
out.put((byte)0);
out.put((byte)0);
out.put((byte)0);
} else {
// Partially transparent/inverted pixel
// We _really_ can't handle this, just make it black
out.put((byte)0xff);
out.put((byte)0);
out.put((byte)0);
out.put((byte)0);
}
}
}
handler.setCursor(width, height, hotspot, data.array());
} else if (type == 1) {
ByteBuffer data = ByteBuffer.allocate(width*height*4);
// FIXME: Is alpha premultiplied?
ByteBuffer buf = ByteBuffer.allocate(4);
for (int i=0;i < width*height*4;i+=4) {
is.readBytes(buf,4);
data.put(buf.array(),3,1);
data.put(buf.array(),0,3);
buf.clear();
}
handler.setCursor(width, height, hotspot, data.array());
} else {
throw new Exception("Unknown cursor type");
}
}
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.server.pf().bpp / 8);
return imageBuf;
}
public InStream getInStream() { return is; }
public int imageBufIdealSize;
protected CMsgHandler handler;
protected InStream is;
protected int nUpdateRectsLeft;
protected final int maxCursorSize = 256;
protected int[] imageBuf;
protected int imageBufSize;
}