Now using true inbound instruction stream ... no more event-specific servlets.

This commit is contained in:
Michael Jumper
2010-09-15 01:18:00 -07:00
parent 96fff8b3de
commit 2c0219c03b
19 changed files with 95 additions and 944 deletions

View File

@@ -19,17 +19,14 @@ package net.sourceforge.guacamole;
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import net.sourceforge.guacamole.instruction.Instruction;
import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.event.KeyEvent; import net.sourceforge.guacamole.event.KeyEvent;
import net.sourceforge.guacamole.event.PointerEvent; import net.sourceforge.guacamole.event.PointerEvent;
public abstract class Client { public abstract class Client {
public abstract void send(KeyEvent event) throws GuacamoleException; public abstract void write(char[] chunk, int off, int len) throws GuacamoleException;
public abstract void send(PointerEvent event) throws GuacamoleException; public abstract char[] read() throws GuacamoleException;
public abstract void setClipboard(String clipboard) throws GuacamoleException;
public abstract void disconnect() throws GuacamoleException; public abstract void disconnect() throws GuacamoleException;
public abstract Instruction nextInstruction(boolean blocking) throws GuacamoleException;
} }

View File

@@ -31,7 +31,6 @@ import java.io.OutputStream;
import java.io.Writer; import java.io.Writer;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import net.sourceforge.guacamole.instruction.Instruction;
import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.event.EventQueue; import net.sourceforge.guacamole.event.EventQueue;
import net.sourceforge.guacamole.event.EventHandler; import net.sourceforge.guacamole.event.EventHandler;
@@ -57,64 +56,9 @@ public class GuacamoleClient extends Client {
} }
public void write(char[] chunk, int off, int len) throws GuacamoleException {
private static final int EVENT_DEADLINE = 500;
private EventQueue<KeyEvent> keyEvents = new EventQueue<KeyEvent>(new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) throws IOException {
int pressed = 0;
if (event.getPressed()) pressed = 1;
output.write("key:" + event.getKeySym() + "," + pressed + ";");
output.flush();
}
}, EVENT_DEADLINE);
private EventQueue<PointerEvent> pointerEvents = new EventQueue<PointerEvent>(new EventHandler<PointerEvent>() {
public void handle(PointerEvent event) throws IOException {
int mask = 0;
if (event.isLeftButtonPressed()) mask |= 1;
if (event.isMiddleButtonPressed()) mask |= 2;
if (event.isRightButtonPressed()) mask |= 4;
if (event.isUpButtonPressed()) mask |= 8;
if (event.isDownButtonPressed()) mask |= 16;
output.write("mouse:" + event.getX() + "," + event.getY() + "," + mask + ";");
output.flush();
}
}, EVENT_DEADLINE);
public void send(KeyEvent event) throws GuacamoleException {
try { try {
keyEvents.add(event); output.write(chunk, off, len);
}
catch (IOException e) {
throw new GuacamoleException(e);
}
}
public void send(PointerEvent event) throws GuacamoleException {
try {
pointerEvents.add(event);
}
catch (IOException e) {
throw new GuacamoleException(e);
}
}
public void setClipboard(String clipboard) throws GuacamoleException {
try {
output.write("clipboard:" + Instruction.escape(clipboard) + ";");
output.flush(); output.flush();
} }
catch (IOException e) { catch (IOException e) {
@@ -134,7 +78,7 @@ public class GuacamoleClient extends Client {
private int usedLength = 0; private int usedLength = 0;
private char[] buffer = new char[20000]; private char[] buffer = new char[20000];
public Instruction nextInstruction(boolean blocking) throws GuacamoleException { public char[] read() throws GuacamoleException {
try { try {
@@ -164,20 +108,15 @@ public class GuacamoleClient extends Client {
if (readChar == ';') { if (readChar == ';') {
// Get instruction // Get instruction
final String instruction = new String(buffer, 0, i+1); char[] chunk = new char[i+1];
System.arraycopy(buffer, 0, chunk, 0, i+1);
// Reset buffer // Reset buffer
usedLength -= i+1; usedLength -= i+1;
System.arraycopy(buffer, i+1, buffer, 0, usedLength); System.arraycopy(buffer, i+1, buffer, 0, usedLength);
// Return instruction string wrapped in Instruction class // Return instruction string
return new Instruction() { return chunk;
public String toString() {
return instruction;
}
};
} }
} }

View File

@@ -1,39 +0,0 @@
package net.sourceforge.guacamole.instruction;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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()) + ";";
}
}

View File

@@ -1,39 +0,0 @@
package net.sourceforge.guacamole.instruction;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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()) + ";";
}
}

View File

@@ -1,63 +0,0 @@
package net.sourceforge.guacamole.instruction;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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 static String escape(String str) {
StringBuffer sb = new StringBuffer();
for (int i=0; i<str.length(); i++) {
char c = str.charAt(i);
switch (c) {
case ',':
sb.append("\\c");
break;
case ';':
sb.append("\\s");
break;
case '\\':
sb.append("\\\\");
break;
default:
sb.append(c);
}
}
return sb.toString();
}
}

View File

@@ -1,39 +0,0 @@
package net.sourceforge.guacamole.instruction;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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()) + ";";
}
}

View File

@@ -1,47 +0,0 @@
package net.sourceforge.guacamole.instruction;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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() + ";";
}
}

View File

@@ -1,78 +0,0 @@
package net.sourceforge.guacamole.instruction.framebuffer;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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() + ";";
}
}

View File

@@ -1,66 +0,0 @@
package net.sourceforge.guacamole.instruction.framebuffer;
import net.sourceforge.guacamole.net.Base64;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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()) + ";";
}
}

View File

@@ -1,72 +0,0 @@
package net.sourceforge.guacamole.instruction.framebuffer;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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()) + ";";
}
}

View File

@@ -1,95 +0,0 @@
package net.sourceforge.guacamole.instruction.framebuffer;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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<ImageWriter> 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();
}
}

View File

@@ -1,65 +0,0 @@
package net.sourceforge.guacamole.instruction.framebuffer;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
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()) + ";";
}
}

View File

@@ -27,7 +27,6 @@ import javax.servlet.http.HttpSessionBindingListener;
import net.sourceforge.guacamole.Client; import net.sourceforge.guacamole.Client;
import net.sourceforge.guacamole.GuacamoleClient; import net.sourceforge.guacamole.GuacamoleClient;
import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.instruction.Instruction;
import net.sourceforge.guacamole.event.KeyEvent; import net.sourceforge.guacamole.event.KeyEvent;
import net.sourceforge.guacamole.event.PointerEvent; import net.sourceforge.guacamole.event.PointerEvent;
@@ -59,26 +58,18 @@ public class GuacamoleSession {
} }
} }
public void send(KeyEvent event) throws GuacamoleException { public void write(char[] data, int off, int len) throws GuacamoleException {
client.send(event); client.write(data, off, len);
} }
public void send(PointerEvent event) throws GuacamoleException { public char[] read() throws GuacamoleException {
client.send(event); return client.read();
}
public void setClipboard(String clipboard) throws GuacamoleException {
client.setClipboard(clipboard);
} }
public void disconnect() throws GuacamoleException { public void disconnect() throws GuacamoleException {
client.disconnect(); client.disconnect();
} }
public Instruction nextInstruction(boolean blocking) throws GuacamoleException {
return client.nextInstruction(blocking);
}
} }
public GuacamoleSession(HttpSession session) throws GuacamoleException { public GuacamoleSession(HttpSession session) throws GuacamoleException {

View File

@@ -1,4 +1,3 @@
package net.sourceforge.guacamole.net.input; package net.sourceforge.guacamole.net.input;
/* /*
@@ -23,46 +22,37 @@ import javax.servlet.ServletRequest;
import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.GuacamoleException;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import java.io.Reader;
import java.io.IOException;
import net.sourceforge.guacamole.net.GuacamoleSession; import net.sourceforge.guacamole.net.GuacamoleSession;
import net.sourceforge.guacamole.net.XMLGuacamoleServlet; import net.sourceforge.guacamole.net.XMLGuacamoleServlet;
import java.io.IOException; public class Inbound extends XMLGuacamoleServlet {
import java.io.Reader;
/**
* Servlet which sets the clipboard data.
*
* This servlet takes one parameter:
* data: The data to set the clipboard to.
*
* @author Michael Jumper
*/
public class Clipboard extends XMLGuacamoleServlet {
@Override @Override
protected void handleRequest(GuacamoleSession session, ServletRequest request, Element root) throws GuacamoleException { protected void handleRequest(GuacamoleSession session, ServletRequest request, Element root) throws GuacamoleException {
// Send data
try { try {
// Read data from request body Reader input = request.getReader();
Reader reader = request.getReader(); char[] buffer = new char[8192];
StringBuilder data = new StringBuilder();
int codepoint; int length;
while ((codepoint = reader.read()) != -1) while ((length = input.read(buffer, 0, buffer.length)) != -1)
data.appendCodePoint(codepoint); session.getClient().write(buffer, 0, length);
// Set clipboard
session.getClient().setClipboard(data.toString());
} }
catch (IOException e) { catch (IOException e) {
throw new GuacamoleException("I/O error sending clipboard to server: " + e.getMessage(), e); throw new GuacamoleException("I/O Error sending data to server: " + e.getMessage(), e);
} }
catch (GuacamoleException e) { catch (GuacamoleException e) {
throw new GuacamoleException("Error sending clipboard to server: " + e.getMessage(), e); throw new GuacamoleException("Error sending data to server: " + e.getMessage(), e);
} }
} }
} }

View File

@@ -1,66 +0,0 @@
package net.sourceforge.guacamole.net.input;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
import javax.servlet.ServletRequest;
import net.sourceforge.guacamole.GuacamoleException;
import org.w3c.dom.Element;
import net.sourceforge.guacamole.event.KeyEvent;
import net.sourceforge.guacamole.net.GuacamoleSession;
import net.sourceforge.guacamole.net.XMLGuacamoleServlet;
/**
* Servlet which accepts keyboard input events, forwards these events to the
* client associated with the session, and returns the result (if any)
* to the HTTP client via XML.
*
* This servlet takes three parameters:
* index: The event index. As HTTP requests may arrive out of order,
* this index provides the event queue with a means of sorting
* events, and determining if events are missing. The first
* event has index 0.
* pressed: Whether the key was pressed (1) or released (0).
* keysym: The integer representing the corresponding X11 keysym.
*
* @author Michael Jumper
*/
public class Key extends XMLGuacamoleServlet {
@Override
protected void handleRequest(GuacamoleSession session, ServletRequest request, Element root) throws GuacamoleException {
// Event parameters
int index = Integer.parseInt(request.getParameter("index"));
boolean pressed = request.getParameter("pressed").equals("1");
int keysym = Integer.parseInt(request.getParameter("keysym"));
// Send/queue event
try {
session.getClient().send(new KeyEvent(index, keysym, pressed));
}
catch (GuacamoleException e) {
throw new GuacamoleException("Error sending key event to server: " + e.getMessage(), e);
}
}
}

View File

@@ -1,63 +0,0 @@
package net.sourceforge.guacamole.net.input;
/*
* Guacamole - Clientless Remote Desktop
* 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 <http://www.gnu.org/licenses/>.
*/
import javax.servlet.ServletRequest;
import net.sourceforge.guacamole.GuacamoleException;
import org.w3c.dom.Element;
import net.sourceforge.guacamole.event.PointerEvent;
import net.sourceforge.guacamole.net.GuacamoleSession;
import net.sourceforge.guacamole.net.XMLGuacamoleServlet;
public class Pointer extends XMLGuacamoleServlet {
@Override
protected void handleRequest(GuacamoleSession session, ServletRequest request, Element root) throws GuacamoleException {
// Event parameters
String[] events = request.getParameterValues("event");
for (String event : events) {
String[] parameters = event.split(",");
int index = Integer.parseInt(parameters[0]);
int x = Integer.parseInt(parameters[1]);
int y = Integer.parseInt(parameters[2]);
boolean left = parameters[3].equals("1");
boolean middle = parameters[4].equals("1");
boolean right = parameters[5].equals("1");
boolean up = parameters[6].equals("1");
boolean down = parameters[7].equals("1");
// Store event
try {
session.getClient().send(new PointerEvent(index, left, middle, right, up, down, x, y));
}
catch (GuacamoleException e) {
throw new GuacamoleException("Error sending pointer event to server: " + e.getMessage(), e);
}
}
}
}

View File

@@ -18,12 +18,9 @@ package net.sourceforge.guacamole.net.output;
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import java.io.OutputStream; import java.io.Writer;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@@ -31,8 +28,6 @@ import net.sourceforge.guacamole.Client;
import net.sourceforge.guacamole.net.GuacamoleServlet; import net.sourceforge.guacamole.net.GuacamoleServlet;
import net.sourceforge.guacamole.GuacamoleException; import net.sourceforge.guacamole.GuacamoleException;
import net.sourceforge.guacamole.net.GuacamoleSession; import net.sourceforge.guacamole.net.GuacamoleSession;
import net.sourceforge.guacamole.instruction.Instruction;
import net.sourceforge.guacamole.instruction.ErrorInstruction;
public class InstructionStream extends GuacamoleServlet { public class InstructionStream extends GuacamoleServlet {
@@ -46,36 +41,7 @@ public class InstructionStream extends GuacamoleServlet {
try { try {
response.setContentType("text/plain"); response.setContentType("text/plain");
OutputStream out = response.getOutputStream(); Writer out = response.getWriter();
// Compress if enabled and supported by browser
if (session.getConfiguration().getCompressStream()) {
String encodingHeader = request.getHeader("Accept-Encoding");
if (encodingHeader != null) {
String[] encodings = encodingHeader.split(",");
for (String encoding : encodings) {
// Use gzip if supported
if (encoding.equals("gzip")) {
response.setHeader("Content-Encoding", "gzip");
out = new GZIPOutputStream(out);
break;
}
// Use deflate if supported
if (encoding.equals("deflate")) {
response.setHeader("Content-Encoding", "deflate");
out = new DeflaterOutputStream(out);
break;
}
}
}
}
try { try {
@@ -83,12 +49,11 @@ public class InstructionStream extends GuacamoleServlet {
Client client = session.getClient(); Client client = session.getClient();
// For all messages, until another stream is ready (we send at least one message) // For all messages, until another stream is ready (we send at least one message)
Instruction message = client.nextInstruction(true); // Block until first message is read char[] message;
while (message != null) { while ((message = client.read()) != null) {
// Get message output bytes // Get message output bytes
byte[] outputBytes = message.toString().getBytes("UTF-8"); out.write(message, 0, message.length);
out.write(outputBytes);
out.flush(); out.flush();
response.flushBuffer(); response.flushBuffer();
@@ -96,14 +61,11 @@ public class InstructionStream extends GuacamoleServlet {
if (instructionStreamLock.hasQueuedThreads()) if (instructionStreamLock.hasQueuedThreads())
break; break;
message = client.nextInstruction(false); // Read remaining messages, do not block.
} }
} }
catch (GuacamoleException e) { catch (GuacamoleException e) {
Instruction message = new ErrorInstruction(e.getMessage()); out.write("error:" + e.getMessage() + ";");
byte[] outputBytes = message.toString().getBytes("UTF-8");
out.write(outputBytes);
out.flush(); out.flush();
response.flushBuffer(); response.flushBuffer();
} }

View File

@@ -53,31 +53,13 @@
<url-pattern>/instructions</url-pattern> <url-pattern>/instructions</url-pattern>
</servlet-mapping> </servlet-mapping>
<servlet> <servlet>
<description>Clipboard input servlet.</description> <description>Input servlet.</description>
<servlet-name>Clipboard</servlet-name> <servlet-name>Inbound</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.input.Clipboard</servlet-class> <servlet-class>net.sourceforge.guacamole.net.input.Inbound</servlet-class>
</servlet> </servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>Clipboard</servlet-name> <servlet-name>Inbound</servlet-name>
<url-pattern>/clipboard</url-pattern> <url-pattern>/inbound</url-pattern>
</servlet-mapping>
<servlet>
<description>Key input servlet.</description>
<servlet-name>Key</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.input.Key</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Key</servlet-name>
<url-pattern>/key</url-pattern>
</servlet-mapping>
<servlet>
<description>Pointer input servlet.</description>
<servlet-name>Pointer</servlet-name>
<servlet-class>net.sourceforge.guacamole.net.input.Pointer</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Pointer</servlet-name>
<url-pattern>/pointer</url-pattern>
</servlet-mapping> </servlet-mapping>
<security-constraint> <security-constraint>
<display-name>Guacamole Access Restrictions</display-name> <display-name>Guacamole Access Restrictions</display-name>

View File

@@ -46,9 +46,6 @@ function VNCClient(display) {
|| currentState == STATE_WAITING; || currentState == STATE_WAITING;
} }
var keyIndex = 0;
var xmlIndex = 0;
// Layers // Layers
var background = null; var background = null;
var cursor = null; var cursor = null;
@@ -118,17 +115,7 @@ function VNCClient(display) {
this.enableKeyboard(); this.enableKeyboard();
function sendKeyEvent(pressed, keysym) { function sendKeyEvent(pressed, keysym) {
sendMessage("key:" + keysym + "," + pressed + ";");
// Do not send requests if not connected
if (!isConnected())
return;
var key_xmlhttprequest = new XMLHttpRequest();
key_xmlhttprequest.open("GET",
"key?index=" + (keyIndex++)
+ "&pressed=" + pressed
+ "&keysym=" + keysym);
key_xmlhttprequest.send(null);
} }
this.pressKey = function(keysym) { this.pressKey = function(keysym) {
@@ -170,47 +157,63 @@ function VNCClient(display) {
); );
var sendingMouseEvents = 0;
var mouseEventBuffer = "";
function sendMouseState(mouseState) { function sendMouseState(mouseState) {
// Build mask
var buttonMask = 0;
if (mouseState.getLeft()) buttonMask |= 1;
if (mouseState.getMiddle()) buttonMask |= 2;
if (mouseState.getRight()) buttonMask |= 4;
if (mouseState.getUp()) buttonMask |= 8;
if (mouseState.getDown()) buttonMask |= 16;
// Send message
sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";");
}
var sendingMessages = 0;
var outputMessageBuffer = "";
function sendMessage(message) {
// Do not send requests if not connected // Do not send requests if not connected
if (!isConnected()) if (!isConnected())
return; return;
// Add event to queue, restart send loop if finished. // Add event to queue, restart send loop if finished.
if (mouseEventBuffer.length > 0) mouseEventBuffer += "&"; outputMessageBuffer += message;
mouseEventBuffer += "event=" + mouseState.toString(); if (sendingMessages == 0)
if (sendingMouseEvents == 0) sendPendingMessages();
sendPendingMouseEvents();
} }
function sendPendingMouseEvents() { function sendPendingMessages() {
// Do not send requests if not connected // Do not send requests if not connected
if (!isConnected()) if (!isConnected())
return; return;
if (mouseEventBuffer.length > 0) { if (outputMessageBuffer.length > 0) {
sendingMouseEvents = 1; sendingMessages = 1;
var mouse_xmlhttprequest = new XMLHttpRequest(); var message_xmlhttprequest = new XMLHttpRequest();
mouse_xmlhttprequest.open("GET", "pointer?" + mouseEventBuffer); message_xmlhttprequest.open("POST", "inbound");
mouseEventBuffer = ""; // Clear buffer message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
message_xmlhttprequest.setRequestHeader("Content-length", outputMessageBuffer.length);
// Once response received, send next queued event. // Once response received, send next queued event.
mouse_xmlhttprequest.onreadystatechange = function() { message_xmlhttprequest.onreadystatechange = function() {
if (mouse_xmlhttprequest.readyState == 4) if (message_xmlhttprequest.readyState == 4)
sendPendingMouseEvents(); sendPendingMessages();
} }
mouse_xmlhttprequest.send(null); message_xmlhttprequest.send(outputMessageBuffer);
outputMessageBuffer = ""; // Clear buffer
} }
else else
sendingMouseEvents = 0; sendingMessages = 0;
} }
@@ -225,10 +228,7 @@ function VNCClient(display) {
if (!isConnected()) if (!isConnected())
return; return;
var clipboard_xmlhttprequest = new XMLHttpRequest(); sendMessage("clipboard:" + escapeGuacamoleString(data) + ";");
clipboard_xmlhttprequest.open("POST", "clipboard");
clipboard_xmlhttprequest.send(data);
} }
@@ -387,6 +387,28 @@ function VNCClient(display) {
} }
function escapeGuacamoleString(str) {
var escapedString = "";
for (var i=0; i<str.length; i++) {
var c = str.charAt(i);
if (c == ",")
escapedString += "\\c";
else if (c == ";")
escapedString += "\\s";
else if (c == "\\")
escapedString += "\\\\";
else
escapedString += c;
}
return escapedString;
}
function unescapeGuacamoleString(str) { function unescapeGuacamoleString(str) {
var unescapedString = ""; var unescapedString = "";