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