diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/LegacyClient.java b/guacamole/web-client/src/net/sourceforge/guacamole/LegacyClient.java
new file mode 100644
index 000000000..83f680098
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/LegacyClient.java
@@ -0,0 +1,35 @@
+
+package net.sourceforge.guacamole;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import net.sourceforge.guacamole.instruction.Instruction;
+import net.sourceforge.guacamole.GuacamoleException;
+import net.sourceforge.guacamole.event.KeyEvent;
+import net.sourceforge.guacamole.event.PointerEvent;
+
+public abstract class LegacyClient {
+
+ public abstract void send(KeyEvent event) throws GuacamoleException;
+ public abstract void send(PointerEvent event) throws GuacamoleException;
+ public abstract void setClipboard(String clipboard) throws GuacamoleException;
+ public abstract void disconnect() throws GuacamoleException;
+ public abstract Instruction nextInstruction(boolean blocking) throws GuacamoleException;
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/ClipboardInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/ClipboardInstruction.java
new file mode 100644
index 000000000..f5e62d1ab
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/ClipboardInstruction.java
@@ -0,0 +1,39 @@
+
+package net.sourceforge.guacamole.instruction;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+public class ClipboardInstruction extends Instruction {
+
+ private String data;
+
+ public ClipboardInstruction(String data) {
+ this.data = data;
+ }
+
+ public String getData() {
+ return data;
+ }
+
+ @Override
+ public String toString() {
+ return "clipboard:" + escape(getData()) + ";";
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/ErrorInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/ErrorInstruction.java
new file mode 100644
index 000000000..36c337784
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/ErrorInstruction.java
@@ -0,0 +1,39 @@
+
+package net.sourceforge.guacamole.instruction;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+public class ErrorInstruction extends Instruction {
+
+ private String error;
+
+ public ErrorInstruction(String error) {
+ this.error = error;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ @Override
+ public String toString() {
+ return "error:" + escape(getError()) + ";";
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/Instruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/Instruction.java
new file mode 100644
index 000000000..b7ed3553f
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/Instruction.java
@@ -0,0 +1,63 @@
+
+package net.sourceforge.guacamole.instruction;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+public abstract class Instruction {
+
+
+ // All Instructions must provide a toString() implementation
+ // which returns the properly formatted instruction:
+ // OPCODE:parm1,parm2,...,parmN;
+
+ @Override
+ public abstract String toString();
+
+ public String escape(String str) {
+
+ StringBuffer sb = new StringBuffer();
+
+ for (int i=0; i.
+ */
+
+public class NameInstruction extends Instruction {
+
+ private String name;
+
+ public NameInstruction(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "name:" + escape(getName()) + ";";
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/SizeInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/SizeInstruction.java
new file mode 100644
index 000000000..4d4ece13c
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/SizeInstruction.java
@@ -0,0 +1,47 @@
+
+package net.sourceforge.guacamole.instruction;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+public class SizeInstruction extends Instruction {
+
+ private int width;
+ private int height;
+
+ public SizeInstruction(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public String toString() {
+ return "size:"
+ + getWidth() + ","
+ + getHeight() + ";";
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/CopyRectInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/CopyRectInstruction.java
new file mode 100644
index 000000000..42cc3b930
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/CopyRectInstruction.java
@@ -0,0 +1,78 @@
+
+package net.sourceforge.guacamole.instruction.framebuffer;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import net.sourceforge.guacamole.instruction.Instruction;
+
+public class CopyRectInstruction extends Instruction {
+
+ private final int x;
+ private final int y;
+ private final int width;
+ private final int height;
+
+ private final int srcX;
+ private final int srcY;
+
+ public CopyRectInstruction(int x, int y, int width, int height, int srcX, int srcY) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.srcX = srcX;
+ this.srcY = srcY;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getSrcX() {
+ return srcX;
+ }
+
+ public int getSrcY() {
+ return srcY;
+ }
+
+ @Override
+ public String toString() {
+ return "copy:"
+ + getSrcX() + ","
+ + getSrcY() + ","
+ + getWidth() + ","
+ + getHeight() + ","
+ + getX() + ","
+ + getY() + ";";
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/CursorInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/CursorInstruction.java
new file mode 100644
index 000000000..9122f6f6b
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/CursorInstruction.java
@@ -0,0 +1,66 @@
+package net.sourceforge.guacamole.instruction.framebuffer;
+
+import net.sourceforge.guacamole.net.Base64;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import net.sourceforge.guacamole.instruction.Instruction;
+
+public class CursorInstruction extends Instruction {
+
+ private int x;
+ private int y;
+ private PNGImage image;
+
+ public CursorInstruction(int x, int y, PNGImage image) {
+ this.x = x;
+ this.y = y;
+ this.image = image;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public PNGImage getImage() {
+ return image;
+ }
+
+ public int getWidth() {
+ return getImage().getWidth();
+ }
+
+ public int getHeight() {
+ return getImage().getHeight();
+ }
+
+ @Override
+ public String toString() {
+ return "cursor:"
+ + getX() + ","
+ + getY() + ","
+ + Base64.toString(getImage().getData()) + ";";
+ }
+
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/DrawRectInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/DrawRectInstruction.java
new file mode 100644
index 000000000..5da8ac271
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/DrawRectInstruction.java
@@ -0,0 +1,72 @@
+
+package net.sourceforge.guacamole.instruction.framebuffer;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import net.sourceforge.guacamole.instruction.Instruction;
+
+public class DrawRectInstruction extends Instruction {
+
+ private final int x;
+ private final int y;
+ private final int width;
+ private final int height;
+ private final int color;
+
+ public DrawRectInstruction(int x, int y, int width, int height, int color) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ this.color = color;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getColor() {
+ return color;
+ }
+
+ @Override
+ public String toString() {
+
+ return "rect:"
+ + getX() + ","
+ + getY() + ","
+ + getWidth() + ","
+ + getHeight() + ","
+ + String.format("#%06X", getColor()) + ";";
+
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/PNGImage.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/PNGImage.java
new file mode 100644
index 000000000..4a74d4a5e
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/PNGImage.java
@@ -0,0 +1,95 @@
+package net.sourceforge.guacamole.instruction.framebuffer;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageOutputStream;
+import net.sourceforge.guacamole.GuacamoleException;
+
+public class PNGImage {
+
+ private int width;
+ private int height;
+ private byte[] data;
+
+ public PNGImage(BufferedImage image) throws GuacamoleException {
+
+ width = image.getWidth();
+ height = image.getHeight();
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+ try {
+ writeImage(image, bos);
+ bos.flush();
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("I/O Error while creating PNG.", e);
+ }
+
+ data = bos.toByteArray();
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ private static void writeImage(BufferedImage image, OutputStream outputStream) throws GuacamoleException, IOException {
+
+ // Obtain list of image writers
+ // If no such writers exist, fail with error, exit.
+ Iterator writers = ImageIO.getImageWritersByMIMEType("image/png");
+ if (!writers.hasNext())
+ throw new GuacamoleException("No useful image writers found.");
+
+ // Obtain JPEG writer
+ ImageWriter imageWriter = writers.next();
+
+ // Setup image parameters (including compression quality)
+ /*ImageWriteParam imageParameters = new JPEGImageWriteParam(Locale.ENGLISH);
+ imageParameters.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+ imageParameters.setCompressionQuality(0.6f); // 60% quality, currently...
+ imageParameters.setProgressiveMode(ImageWriteParam.MODE_DEFAULT);*/
+
+ ImageOutputStream out = ImageIO.createImageOutputStream(outputStream);
+
+ // Write image
+ imageWriter.setOutput(out);
+ imageWriter.write(null, new IIOImage(image, null, null), null/*imageParameters*/);
+ imageWriter.dispose();
+
+ out.flush();
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/PNGInstruction.java b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/PNGInstruction.java
new file mode 100644
index 000000000..b7f113489
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/instruction/framebuffer/PNGInstruction.java
@@ -0,0 +1,65 @@
+package net.sourceforge.guacamole.instruction.framebuffer;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import net.sourceforge.guacamole.instruction.Instruction;
+import net.sourceforge.guacamole.net.Base64;
+
+public class PNGInstruction extends Instruction {
+
+ private int x;
+ private int y;
+ private PNGImage image;
+
+ public PNGInstruction(int x, int y, PNGImage image) {
+ this.x = x;
+ this.y = y;
+ this.image = image;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public PNGImage getImage() {
+ return image;
+ }
+
+ public int getWidth() {
+ return getImage().getWidth();
+ }
+
+ public int getHeight() {
+ return getImage().getHeight();
+ }
+
+ @Override
+ public String toString() {
+ return "png:"
+ + getX() + ","
+ + getY() + ","
+ + Base64.toString(getImage().getData()) + ";";
+ }
+
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/vnc/InputOutputStream.java b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/InputOutputStream.java
new file mode 100644
index 000000000..a796522e9
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/InputOutputStream.java
@@ -0,0 +1,63 @@
+
+package net.sourceforge.guacamole.vnc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+
+public class InputOutputStream extends InputStream {
+
+ private int pos = 0;
+ private byte[] current = null;
+ private LinkedList buffer = new LinkedList();
+
+ public void write(byte[] data) {
+
+ if (data.length == 0)
+ return;
+
+ if (current == null)
+ current = data;
+ else
+ buffer.addLast(data);
+ }
+
+ @Override
+ public int read() throws IOException {
+
+ if (pos >= current.length) {
+ if (buffer.size() == 0)
+ throw new IOException("Buffer underrun.");
+
+ current = buffer.removeFirst();
+ pos = 0;
+ }
+
+ return 0xFF & current[pos++];
+
+ }
+
+ @Override
+ public int read(byte[] data) throws IOException {
+ return read(data, 0, data.length);
+ }
+
+ @Override
+ public int read(byte[] data, int off, int len) throws IOException {
+
+ if (pos >= current.length) {
+ if (buffer.size() == 0)
+ throw new IOException("Buffer underrun.");
+
+ current = buffer.removeFirst();
+ pos = 0;
+ }
+
+ int amountRead = Math.min(current.length - pos, len);
+ System.arraycopy(current, pos, data, off, amountRead);
+ pos += amountRead;
+
+ return amountRead;
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCClient.java b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCClient.java
new file mode 100644
index 000000000..4f5783895
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCClient.java
@@ -0,0 +1,1113 @@
+package net.sourceforge.guacamole.vnc;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import java.awt.image.WritableRaster;
+import net.sourceforge.guacamole.instruction.framebuffer.PNGInstruction;
+import net.sourceforge.guacamole.instruction.framebuffer.CursorInstruction;
+import net.sourceforge.guacamole.instruction.framebuffer.CopyRectInstruction;
+import net.sourceforge.guacamole.event.PointerEvent;
+import net.sourceforge.guacamole.event.EventQueue;
+import net.sourceforge.guacamole.event.KeyEvent;
+import net.sourceforge.guacamole.event.EventHandler;
+import java.awt.image.BufferedImage;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import java.net.Socket;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.DESKeySpec;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.BadPaddingException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.Inflater;
+import net.sourceforge.guacamole.LegacyClient;
+
+import net.sourceforge.guacamole.instruction.ClipboardInstruction;
+import net.sourceforge.guacamole.instruction.Instruction;
+import net.sourceforge.guacamole.instruction.NameInstruction;
+import net.sourceforge.guacamole.instruction.SizeInstruction;
+import net.sourceforge.guacamole.instruction.framebuffer.PNGImage;
+import net.sourceforge.guacamole.GuacamoleException;
+
+public class VNCClient extends LegacyClient {
+
+ private static final int SECURITY_TYPE_INVALID = 0;
+ private static final int SECURITY_TYPE_NONE = 1;
+ private static final int SECURITY_TYPE_VNC_AUTHENTICATION = 2;
+
+ private static final int SECURITY_RESULT_OK = 0;
+ private static final int SECURITY_RESULT_FAILED = 1;
+
+ private static final int MESSAGE_SET_PIXEL_FORMAT = 0;
+ private static final int MESSAGE_SET_ENCODINGS = 2;
+ private static final int MESSAGE_FRAMEBUFFER_UPDATE_REQUEST = 3;
+ private static final int MESSAGE_KEY_EVENT = 4;
+ private static final int MESSAGE_POINTER_EVENT = 5;
+ private static final int MESSAGE_CLIENT_CUT_TEXT = 6;
+
+ private static final int MESSAGE_FRAMEBUFFER_UPDATE = 0;
+ private static final int MESSAGE_SET_COLORMAP_ENTRIES = 1;
+ private static final int MESSAGE_BELL = 2;
+ private static final int MESSAGE_SERVER_CUT_TEXT = 3;
+
+ private static final int HEXTILE_FLAG_RAW = 1;
+ private static final int HEXTILE_FLAG_BACKGROUND_SPECIFIED = 2;
+ private static final int HEXTILE_FLAG_FOREGROUND_SPECIFIED = 4;
+ private static final int HEXTILE_FLAG_ANY_SUBRECTS = 8;
+ private static final int HEXTILE_FLAG_SUBRECTS_COLORED = 16;
+
+ private static final int ENCODING_RAW = 0;
+ private static final int ENCODING_COPYRECT = 1;
+ private static final int ENCODING_RRE = 2;
+ private static final int ENCODING_HEXTILE = 5;
+ private static final int ENCODING_ZRLE = 16;
+ private static final int ENCODING_CURSOR = -239;
+
+ private final Socket sock;
+ private final DataInputStream input;
+ private final DataOutputStream output;
+
+ private int frameBufferWidth;
+ private int frameBufferHeight;
+ private String name;
+
+ private boolean needRefresh = true;
+ private VNCImageReader rawReader = null;
+
+ private InputOutputStream toZlib;
+ private DataInputStream fromZlib;
+
+ public int getFrameBufferHeight() {
+ return frameBufferHeight;
+ }
+
+ public int getFrameBufferWidth() {
+ return frameBufferWidth;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public byte reverse(byte b) {
+
+ int input = b & 0xFF;
+ int output = 0;
+
+ for (int i=0; i<8; i++) {
+
+ output <<= 1;
+
+ if ((input & 0x01) != 0)
+ output |= 0x01;
+
+ input >>= 1;
+
+ }
+
+ return (byte) output;
+ }
+
+ // Generates VNC key from string
+ private byte[] generateVNCAuthKey(String password) throws VNCException {
+
+ try {
+ // Get password bytes
+ byte[] passwordBytes = password.getBytes("iso-8859-1");
+ if (passwordBytes.length > 8)
+ throw new VNCException("Password must be 8 characters (bytes) or less.");
+
+ // Reverse bit order of all bytes in array
+ for (int i=0; i 0 && height > 0) {
+ BufferedImage image = rawReader.readImage(input, width, height);
+
+ // Construct FramebufferUpdate
+ PNGInstruction update = new PNGInstruction(dstX, dstY, new PNGImage(image));
+ instructions.addLast(update);
+
+ // If full-screen refresh, reset refresh flag.
+ if (dstX == 0 && dstY == 0 && width == frameBufferWidth && height == frameBufferHeight)
+ needRefresh = false;
+ }
+ }
+ else if (type == ENCODING_COPYRECT) {
+
+ // Read CopyRect encoding
+ int srcX = input.readUnsignedShort();
+ int srcY = input.readUnsignedShort();
+
+ // Construct FramebufferUpdate
+
+ if (width > 0 && height > 0) {
+ CopyRectInstruction update = new CopyRectInstruction(dstX, dstY, width, height, srcX, srcY);
+ instructions.addLast(update);
+ }
+ }
+ else if (type == ENCODING_RRE) {
+
+ int numSubRects = input.readInt();
+ int background = rawReader.readPixel(input);
+
+ BufferedImage image = null;
+
+ if (width > 0 && height > 0) {
+ image = rawReader.generateBlankImage(width, height);
+ }
+
+ //instructions.addLast(new DrawRectInstruction(dstX, dstY, width, height, background));
+ if (image != null) {
+ fillRect(image, 0, 0, width, height, background);
+ }
+
+ for (int j=0; j 0 && height > 0) {
+ BufferedImage image = rawReader.generateBlankImage(width, height);
+ WritableRaster raster = image.getWritableTile(0, 0);
+
+ int backgroundColor = 0;
+ int foregroundColor = 0;
+
+ // For all 16x16 tiles in left-to-right, top-to-bottom order
+ for (int tileOffsetY = 0; tileOffsetY < height; tileOffsetY += 16) {
+
+ int tileHeight = Math.min(16, height - tileOffsetY);
+
+ for (int tileOffsetX = 0; tileOffsetX < width; tileOffsetX += 16) {
+
+ int tileWidth = Math.min(16, width - tileOffsetX);
+
+ int flags = input.read();
+
+ // If RAW flag is set, other flags are irrelevant.
+ if ((flags & HEXTILE_FLAG_RAW) != 0) {
+
+ // Read and draw raw tile
+ BufferedImage tile = rawReader.readImage(input, tileWidth, tileHeight);
+ raster.setRect(tileOffsetX, tileOffsetY, tile.getData());
+
+ }
+
+ // RAW = 0
+ else {
+
+ // If background specified, read pixel value
+ if ((flags & HEXTILE_FLAG_BACKGROUND_SPECIFIED) != 0)
+ backgroundColor = rawReader.readPixel(input);
+
+ // Draw background
+ fillRect(image, tileOffsetX, tileOffsetY, tileWidth, tileHeight, backgroundColor);
+
+ // If foreground specified, read pixel value
+ if ((flags & HEXTILE_FLAG_FOREGROUND_SPECIFIED) != 0)
+ foregroundColor = rawReader.readPixel(input);
+
+ // If subrects present, read subrects
+ if ((flags & HEXTILE_FLAG_ANY_SUBRECTS) != 0) {
+
+ // Read number of subrects, determine whether they are colored
+ int numSubRects = input.read();
+ boolean colored = (flags & HEXTILE_FLAG_SUBRECTS_COLORED) != 0;
+
+ int color = foregroundColor;
+ for (int j=0; j> 4;
+ int y = position & 0x0F;
+
+ // Read dimensions
+ int dimensions = input.read();
+ int w = (dimensions >> 4) + 1;
+ int h = (dimensions & 0x0F) + 1;
+
+ fillRect(image, tileOffsetX+x, tileOffsetY+y, w, h, color);
+ }
+
+ }
+
+ } // end if not raw
+
+ }
+ }
+
+ // Send as png instruction (rects are too inefficient)
+ PNGInstruction update = new PNGInstruction(dstX, dstY, new PNGImage(image));
+ instructions.addLast(update);
+
+ // If full-screen refresh, reset refresh flag.
+ if (dstX == 0 && dstY == 0 && width == frameBufferWidth && height == frameBufferHeight)
+ needRefresh = false;
+ }
+
+ }
+ else if (type == ENCODING_ZRLE) {
+
+ // Read ZLIB data
+ int length = input.readInt();
+ byte[] zlibData = new byte[length];
+ input.readFully(zlibData);
+
+ // Write data to ZLIB stream
+ toZlib.write(zlibData);
+
+
+ if (width > 0 && height > 0) {
+ BufferedImage image = rawReader.generateBlankImage(width, height);
+ WritableRaster raster = image.getWritableTile(0, 0);
+
+ // For all 64x64 tiles in left-to-right, top-to-bottom order
+ for (int tileOffsetY = 0; tileOffsetY < height; tileOffsetY += 64) {
+
+ int tileHeight = Math.min(64, height - tileOffsetY);
+
+ for (int tileOffsetX = 0; tileOffsetX < width; tileOffsetX += 64) {
+
+ int tileWidth = Math.min(64, width - tileOffsetX);
+
+ // Get subencoding type (RLE flag + palette size)
+ int subencodingType = fromZlib.read();
+
+ // If RAW, just read raw image
+ if (subencodingType == 0) {
+
+ // Read and draw raw tile
+ if (image != null) {
+ BufferedImage tile = rawReader.readCImage(fromZlib, tileWidth, tileHeight);
+ raster.setRect(tileOffsetX, tileOffsetY, tile.getData());
+ }
+
+ }
+
+ // If single color...
+ else if (subencodingType == 1) {
+
+ // Read color
+ int color = rawReader.readCPixel(fromZlib);
+
+ // Draw solid rectangle
+ if (image != null) {
+ fillRect(image, tileOffsetX, tileOffsetY, tileWidth, tileHeight, color);
+ }
+
+ }
+
+ // Packed palette
+ else if (subencodingType >= 2 && subencodingType <= 16) {
+
+ int paletteSize = subencodingType;
+ int[] palette = new int[paletteSize];
+
+ // Read palette
+ for (int j=0; j> (8 - indexBits);
+ buffer <<= indexBits;
+ bitsAvailable -= indexBits;
+
+ // Write pixel to image
+ image.setRGB(tileOffsetX+x, tileOffsetY+y, 0xFF000000 | palette[index]);
+
+ }
+ }
+
+ }
+
+ // Plain RLE
+ else if (subencodingType == 128) {
+
+ int color = -1;
+ int runRemaining= 0;
+
+ for (int y=0; y= 130 && subencodingType <= 255) {
+
+ int paletteSize = subencodingType - 128;
+ int[] palette = new int[paletteSize];
+
+ // Read palette
+ for (int j=0; j 0 && height > 0) {
+ BufferedImage image = rawReader.readImage(input, width, height);
+
+ // Read cursor mask
+ for (int y=0; y> 8;
+ int green = input.readUnsignedShort() >> 8;
+ int blue = input.readUnsignedShort() >> 8;
+
+ reds[i] = (byte) red;
+ greens[i] = (byte) green;
+ blues[i] = (byte) blue;
+ alphas[i] = (byte) 0xFF;
+ }
+
+ // Set reader
+ rawReader = new VNCIndexedImageReader(reds, greens, blues);
+ }
+
+ private void handleBell() {
+ // Do nothing, currently
+ // This message has no data, so no need to dummy-read.
+ log("BELL!");
+ }
+
+ private void handleServerCutText() throws IOException {
+
+ byte[] padding = new byte[3];
+ input.readFully(padding);
+
+ int length = input.readInt();
+ byte[] textBytes = new byte[length];
+ input.readFully(textBytes);
+
+ String clipboard = new String(textBytes, "UTF-8");
+ instructions.addLast(new ClipboardInstruction(clipboard));
+ }
+
+ private void setPixelFormat(int bitsPerPixel, int depth,
+ boolean bigEndian, boolean trueColor,
+ int redMax, int greenMax, int blueMax,
+ int redShift, int greenShift, int blueShift)
+ throws IOException {
+
+ synchronized (output) {
+ output.writeByte(MESSAGE_SET_PIXEL_FORMAT);
+ output.writeBytes(" "); // Padding
+ output.writeByte(bitsPerPixel);
+ output.writeByte(depth);
+ output.writeBoolean(bigEndian);
+ output.writeBoolean(trueColor);
+ output.writeShort(redMax);
+ output.writeShort(greenMax);
+ output.writeShort(blueMax);
+ output.writeByte(redShift);
+ output.writeByte(greenShift);
+ output.writeByte(blueShift);
+ output.writeBytes(" "); // Padding
+ output.flush();
+ }
+
+ }
+
+ // Last is most recent message.
+ private final Object instructionLock = new Object();
+ private LinkedList instructions = new LinkedList();
+
+ @Override
+ public void setClipboard(String clipboard) throws GuacamoleException {
+ try {
+ sendClipboard(clipboard);
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Could not send clipboard data to VNC server (network error).", e);
+ }
+ }
+
+ @Override
+ public void send(KeyEvent event) throws GuacamoleException {
+ try {
+ // Add to queue
+ keyEvents.add(event);
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Could not send keyboard event to VNC server (network error).", e);
+ }
+ }
+
+ @Override
+ public void send(PointerEvent event) throws GuacamoleException {
+ try {
+ // Add to queue
+ pointerEvents.add(event);
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Could not send pointer event to VNC server (network error).", e);
+ }
+ }
+
+ private static final int EVENT_DEADLINE = 500;
+
+ private EventQueue keyEvents = new EventQueue(new EventHandler() {
+
+ public void handle(KeyEvent e) throws IOException {
+ sendKeyEvent(e.getPressed(), e.getKeySym());
+ }
+
+ }, EVENT_DEADLINE);
+
+ private EventQueue pointerEvents = new EventQueue(new EventHandler() {
+
+ public void handle(PointerEvent e) throws IOException {
+ sendPointerEvent(
+ e.isLeftButtonPressed(),
+ e.isMiddleButtonPressed(),
+ e.isRightButtonPressed(),
+ e.isUpButtonPressed(),
+ e.isDownButtonPressed(),
+ e.getX(),
+ e.getY()
+ );
+ }
+
+ }, EVENT_DEADLINE);
+
+ private void sendClipboard(String clipboard) throws IOException {
+
+ synchronized (output) {
+ output.writeByte(MESSAGE_CLIENT_CUT_TEXT);
+ output.writeBytes(" "); // Padding
+
+ byte[] encodedString = clipboard.getBytes("UTF-8");
+
+ output.writeInt(encodedString.length);
+ output.write(encodedString);
+ output.flush();
+ }
+
+ }
+
+ private void sendKeyEvent(boolean pressed, int keysym) throws IOException {
+
+ synchronized (output) {
+ output.writeByte(MESSAGE_KEY_EVENT);
+ output.writeBoolean(pressed);
+ output.writeBytes(" "); // Padding
+ output.writeInt(keysym);
+ output.flush();
+ }
+
+ //log("Sent key event.");
+ }
+
+ private void sendPointerEvent(boolean left, boolean middle, boolean right, boolean up, boolean down, int x, int y) throws IOException {
+
+ int buttonMask = 0;
+ if (left) buttonMask |= 1;
+ if (middle) buttonMask |= 2;
+ if (right) buttonMask |= 4;
+ if (up) buttonMask |= 8;
+ if (down) buttonMask |= 16;
+
+ synchronized (output) {
+ output.writeByte(MESSAGE_POINTER_EVENT);
+ output.writeByte(buttonMask);
+ output.writeShort(x);
+ output.writeShort(y);
+ output.flush();
+ }
+
+ //log("Sent pointer event.");
+ }
+
+ private void sendEncodings(int... encodings) throws IOException {
+
+ synchronized (output) {
+ output.writeByte(MESSAGE_SET_ENCODINGS);
+ output.writeBytes(" "); // padding
+ output.writeShort(encodings.length);
+
+ for (int encoding : encodings)
+ output.writeInt(encoding);
+ }
+
+ }
+
+ @Override
+ public Instruction nextInstruction(boolean blocking)
+ throws GuacamoleException {
+
+ synchronized (instructionLock) {
+ if (instructions.size() == 0) {
+
+ if (blocking) {
+ try {
+ // Send framebuffer update request
+ synchronized (output) {
+ output.writeByte(MESSAGE_FRAMEBUFFER_UPDATE_REQUEST);
+ output.writeBoolean(!needRefresh); // Incremental
+ output.writeShort(0); // x
+ output.writeShort(0); // y
+ output.writeShort(frameBufferWidth); // width
+ output.writeShort(frameBufferHeight); // height
+ output.flush();
+ }
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Could not send framebuffer update request to VNC server (network error).", e);
+ }
+ }
+
+ // Handle incoming messages, blocking until one message exists
+ try {
+ handleMessages(blocking);
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Could not read messages from VNC server (network error).", e);
+ }
+ catch (VNCException e) {
+ throw new GuacamoleException(e);
+ }
+
+ }
+
+ // If no messages, return null.
+ if (instructions.size() == 0)
+ return null;
+
+ // Send messages, oldest first
+ return instructions.removeFirst();
+ }
+
+ }
+
+ private void log(String str) {
+ System.err.println(str);
+ }
+
+ @Override
+ public void disconnect() throws GuacamoleException {
+ try {
+
+ // Close event queues
+ keyEvents.close();
+ pointerEvents.close();
+
+ // Close socket
+ sock.close();
+ }
+ catch (IOException e) {
+ throw new GuacamoleException("Network error while closing socket.", e);
+ }
+ }
+}
+
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCConfiguration.java b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCConfiguration.java
new file mode 100644
index 000000000..a0a68ad3a
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCConfiguration.java
@@ -0,0 +1,60 @@
+
+package net.sourceforge.guacamole.vnc;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import net.sourceforge.guacamole.net.Configuration;
+import net.sourceforge.guacamole.GuacamoleException;
+import javax.servlet.ServletContext;
+
+public class VNCConfiguration extends Configuration {
+
+ private String hostname;
+ private int port;
+ private String password;
+ private int bpp;
+
+ public VNCConfiguration(ServletContext context) throws GuacamoleException {
+
+ super(context);
+
+ hostname = readParameter("host", null);
+ port = readIntParameter("port", null);
+ password = context.getInitParameter("password");
+ bpp = readIntParameter("bpp", 24, 8, 16, 24);
+
+ }
+
+ public int getBPP() {
+ return bpp;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCException.java b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCException.java
new file mode 100644
index 000000000..ae14c65bd
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCException.java
@@ -0,0 +1,32 @@
+
+package net.sourceforge.guacamole.vnc;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+public class VNCException extends Exception {
+
+ public VNCException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public VNCException(String message) {
+ super(message);
+ }
+
+}
diff --git a/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCFullColorImageReader.java b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCFullColorImageReader.java
new file mode 100644
index 000000000..9509d4204
--- /dev/null
+++ b/guacamole/web-client/src/net/sourceforge/guacamole/vnc/VNCFullColorImageReader.java
@@ -0,0 +1,239 @@
+package net.sourceforge.guacamole.vnc;
+
+/*
+ * Guacamole - Pure JavaScript/HTML VNC Client
+ * Copyright (C) 2010 Michael Jumper
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+
+public class VNCFullColorImageReader extends VNCImageReader {
+
+ private int bpp;
+ private int depth;
+
+ private int redBits;
+ private int greenBits;
+ private int blueBits;
+
+ private int redMax;
+ private int greenMax;
+ private int blueMax;
+
+ private int redShift;
+ private int greenShift;
+ private int blueShift;
+
+ private boolean readAsIndexed;
+ private boolean bigEndian;
+
+ private boolean swapRedAndBlue;
+
+ public boolean isBigEndian() {
+ return bigEndian;
+ }
+
+ public int getBitsPerPixel() {
+ return bpp;
+ }
+
+ public int getDepth() {
+ return depth;
+ }
+
+ public int getRedMax() {
+ return redMax;
+ }
+
+ public int getGreenMax() {
+ return greenMax;
+ }
+
+ public int getBlueMax() {
+ return blueMax;
+ }
+
+ public int getRedShift() {
+ return redShift;
+ }
+
+ public int getGreenShift() {
+ return greenShift;
+ }
+
+ public int getBlueShift() {
+ return blueShift;
+ }
+
+ // Set up BGR reader
+ public VNCFullColorImageReader(boolean bigEndian, int redBits, int greenBits, int blueBits, int outputBPP, boolean swapRedAndBlue) throws VNCException {
+
+ this.swapRedAndBlue = swapRedAndBlue;
+
+ depth = redBits + greenBits + blueBits;
+
+ if (depth > 0 && depth <= 8)
+ bpp = 8;
+ else if (depth <= 16)
+ bpp = 16;
+ else if (depth <= 32)
+ bpp = 32;
+ else
+ throw new VNCException("Illegal bit depth for VNC images: " + bpp);
+
+ this.redBits = redBits;
+ this.greenBits = greenBits;
+ this.blueBits = blueBits;
+
+ blueMax = (1 << blueBits) - 1;
+ greenMax = (1 << greenBits) - 1;
+ redMax = (1 << redBits) - 1;
+
+ redShift = greenBits + blueBits;
+ greenShift = blueBits;
+ blueShift = 0;
+
+ if (outputBPP == 8)
+ this.readAsIndexed = true;
+ else if (outputBPP == 24)
+ this.readAsIndexed = false;
+ else
+ throw new VNCException("Only 8-bit or 24-bit output is supported.");
+
+ }
+
+ @Override
+ public int readCPixel(DataInputStream input) throws IOException {
+
+ if (redBits != 8 || greenBits != 8 || blueBits != 8)
+ return readPixel(input);
+
+ int red;
+ int green;
+ int blue;
+
+ if (bigEndian) {
+ red = input.read();
+ green = input.read();
+ blue = input.read();
+ }
+ else {
+ blue = input.read();
+ green = input.read();
+ red = input.read();
+ }
+
+ if (swapRedAndBlue)
+ return (blue << 16) | (green << 8) | red;
+ else
+ return (red << 16) | (green << 8) | blue;
+ }
+
+ @Override
+ public int readPixel(DataInputStream input) throws IOException {
+ int value;
+ switch (bpp) {
+ case 8:
+ value = input.read();
+ break;
+ case 16:
+
+ short inputShort = input.readShort();
+ if (!bigEndian) inputShort = Short.reverseBytes(inputShort);
+
+ value = inputShort;
+ break;
+ case 32:
+
+ int inputInt = input.readInt();
+ if (!bigEndian) inputInt = Integer.reverseBytes(inputInt);
+
+ value = inputInt;
+ break;
+ default:
+ throw new IOException("Invalid BPP.");
+ }
+
+ int red = (value >> redShift) & redMax;
+ int green = (value >> greenShift) & greenMax;
+ int blue = (value >> blueShift) & blueMax;
+
+ red <<= 8 - redBits;
+ green <<= 8 - greenBits;
+ blue <<= 8 - blueBits;
+
+ if (swapRedAndBlue)
+ return (blue << 16) | (green << 8) | red;
+ else
+ return (red << 16) | (green << 8) | blue;
+ }
+
+ @Override
+ public BufferedImage generateBlankImage(int width, int height) {
+ if (readAsIndexed)
+ return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, COLOR_MODEL);
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+
+ // Load color model
+ public static final IndexColorModel COLOR_MODEL;
+ static {
+ // Construct color model
+ byte[] colorShade = {0, (byte) 51, (byte) 104, (byte) 153, (byte) 204, (byte) 255};
+ byte[] greyShade = new byte[39];
+ for (int i=1; i<40; i++)
+ greyShade[i-1] = (byte) (6*i);
+
+ byte[] red = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+ byte[] green = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+ byte[] blue = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+ byte[] alpha = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+
+ int color = 0;
+ for (int r=0; r.
+ */
+
+import java.awt.image.BufferedImage;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+public abstract class VNCImageReader {
+
+ public BufferedImage readCImage(DataInputStream input, int width, int height) throws IOException {
+
+ BufferedImage image = generateBlankImage(width, height);
+ // Read image
+
+ for (int pixelY=0; pixelY.
+ */
+
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+
+public class VNCIndexedImageReader extends VNCImageReader {
+
+ private IndexColorModel palette;
+ private byte[] red;
+ private byte[] green;
+ private byte[] blue;
+
+ // Set up BGR reader
+ public VNCIndexedImageReader(byte[] red, byte[] green, byte[] blue) throws VNCException {
+
+ this.red = red;
+ this.green = green;
+ this.blue = blue;
+
+ palette = new IndexColorModel(8, 256, red, green, blue);
+ if (palette.getMapSize() != 256)
+ throw new VNCException("Currently, only 256-color maps are supported.");
+
+ }
+
+ @Override
+ public int readPixel(DataInputStream input) throws IOException {
+ int value = input.read();
+ int color = (red[value] << 16) | (green[value] << 8) | blue[value];
+ return color;
+ }
+
+ @Override
+ public BufferedImage generateBlankImage(int width, int height) {
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED, palette);
+ return image;
+ }
+
+ // Load color model
+ public static final IndexColorModel COLOR_MODEL;
+ static {
+ // Construct color model
+ byte[] colorShade = {0, (byte) 51, (byte) 104, (byte) 153, (byte) 204, (byte) 255};
+ byte[] greyShade = new byte[39];
+ for (int i=1; i<40; i++)
+ greyShade[i-1] = (byte) (6*i);
+
+ byte[] red = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+ byte[] green = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+ byte[] blue = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+ byte[] alpha = new byte[colorShade.length*colorShade.length*colorShade.length+greyShade.length+1];
+
+ int color = 0;
+ for (int r=0; r