diff --git a/guacamole/client/Makefile b/guacamole/client/Makefile new file mode 100644 index 000000000..760266a47 --- /dev/null +++ b/guacamole/client/Makefile @@ -0,0 +1,11 @@ + +.PHONY: client clean + +all: client + +client: + ant war + +clean: + ant clean + diff --git a/guacamole/client/ant/build.properties b/guacamole/client/ant/build.properties new file mode 100644 index 000000000..5fa6766cc --- /dev/null +++ b/guacamole/client/ant/build.properties @@ -0,0 +1,13 @@ +servlet.api.jar=/usr/share/tomcat6/lib/servlet-api.jar + +src.dir=src +web.dir=web + +build.dir=build +dist.dir=dist +doc.dir=doc + +guac.version=0.3.0rc1 +tar.dir=guacamole-${guac.version} +tar.src.dir=guacamole-src + diff --git a/guacamole/client/build.xml b/guacamole/client/build.xml new file mode 100644 index 000000000..88685640e --- /dev/null +++ b/guacamole/client/build.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guacamole/client/doc/example/guacamole-users.xml b/guacamole/client/doc/example/guacamole-users.xml new file mode 100644 index 000000000..50bb77a12 --- /dev/null +++ b/guacamole/client/doc/example/guacamole-users.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/guacamole/client/doc/example/guacamole.xml b/guacamole/client/doc/example/guacamole.xml new file mode 100644 index 000000000..1c005f4da --- /dev/null +++ b/guacamole/client/doc/example/guacamole.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/guacamole/client/src/net/sourceforge/guacamole/Client.java b/guacamole/client/src/net/sourceforge/guacamole/Client.java new file mode 100644 index 000000000..93000aaf4 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/Client.java @@ -0,0 +1,32 @@ + +package net.sourceforge.guacamole; + +/* + * 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 . + */ + +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.event.KeyEvent; +import net.sourceforge.guacamole.event.PointerEvent; + +public abstract class Client { + + public abstract void write(char[] chunk, int off, int len) throws GuacamoleException; + public abstract char[] read() throws GuacamoleException; + public abstract void disconnect() throws GuacamoleException; + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/GuacamoleClient.java b/guacamole/client/src/net/sourceforge/guacamole/GuacamoleClient.java new file mode 100644 index 000000000..f79e8a1b4 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/GuacamoleClient.java @@ -0,0 +1,133 @@ + +package net.sourceforge.guacamole; + +/* + * 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 . + */ + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; + +import java.io.InputStream; +import java.io.Reader; +import java.io.InputStreamReader; + +import java.io.OutputStream; +import java.io.Writer; +import java.io.OutputStreamWriter; + +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.event.EventQueue; +import net.sourceforge.guacamole.event.EventHandler; +import net.sourceforge.guacamole.event.KeyEvent; +import net.sourceforge.guacamole.event.PointerEvent; + +public class GuacamoleClient extends Client { + + private Socket sock; + private Reader input; + private Writer output; + + public GuacamoleClient(String hostname, int port) throws GuacamoleException { + + try { + sock = new Socket(InetAddress.getByName(hostname), port); + input = new InputStreamReader(sock.getInputStream()); + output = new OutputStreamWriter(sock.getOutputStream()); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + + } + + public void write(char[] chunk, int off, int len) throws GuacamoleException { + try { + output.write(chunk, off, len); + output.flush(); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + } + + public void disconnect() throws GuacamoleException { + try { + sock.close(); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + } + + private int usedLength = 0; + private char[] buffer = new char[20000]; + + public char[] read() throws GuacamoleException { + + try { + + // While we're blocking, or input is available + for (;;) { + + // If past threshold, resize buffer before reading + if (usedLength > buffer.length/2) { + char[] biggerBuffer = new char[buffer.length*2]; + System.arraycopy(buffer, 0, biggerBuffer, 0, usedLength); + buffer = biggerBuffer; + } + + // Attempt to fill buffer + int numRead = input.read(buffer, usedLength, buffer.length - usedLength); + if (numRead == -1) + return null; + + int prevLength = usedLength; + usedLength += numRead; + + for (int i=usedLength-1; i>=prevLength; i--) { + + char readChar = buffer[i]; + + // If end of instruction, return it. + if (readChar == ';') { + + // Get instruction + char[] chunk = new char[i+1]; + System.arraycopy(buffer, 0, chunk, 0, i+1); + + // Reset buffer + usedLength -= i+1; + System.arraycopy(buffer, i+1, buffer, 0, usedLength); + + // Return instruction string + return chunk; + } + + } + + } // End read loop + + } + catch (IOException e) { + throw new GuacamoleException(e); + } + + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/GuacamoleException.java b/guacamole/client/src/net/sourceforge/guacamole/GuacamoleException.java new file mode 100644 index 000000000..3a09c5ac7 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/GuacamoleException.java @@ -0,0 +1,36 @@ + +package net.sourceforge.guacamole; + +/* + * 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 . + */ + +public class GuacamoleException extends Exception { + + public GuacamoleException(String message, Throwable cause) { + super(message, cause); + } + + public GuacamoleException(String message) { + super(message); + } + + public GuacamoleException(Throwable cause) { + super(cause); + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/event/Event.java b/guacamole/client/src/net/sourceforge/guacamole/event/Event.java new file mode 100644 index 000000000..8fa047f1b --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/event/Event.java @@ -0,0 +1,55 @@ + +package net.sourceforge.guacamole.event; + +/* + * 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 . + */ + +public abstract class Event implements Comparable { + + private long time; + private int index; + + public Event(int index) { + this.time = System.currentTimeMillis(); + this.index = index; + } + + public int getIndex() { + return index; + } + + public long getTime() { + return time; + } + + public int hashCode() { + return index; + } + + @Override + public boolean equals(Object o) { + if (o == null) return false; + if (getClass() != o.getClass()) return false; + return getIndex() == ((Event) o).getIndex(); + } + + public int compareTo(Event e) { + return getIndex() - e.getIndex(); + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/event/EventHandler.java b/guacamole/client/src/net/sourceforge/guacamole/event/EventHandler.java new file mode 100644 index 000000000..bae027a2e --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/event/EventHandler.java @@ -0,0 +1,28 @@ + +package net.sourceforge.guacamole.event; + +/* + * 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 . + */ + +import java.io.IOException; + +public interface EventHandler { + + public void handle(E e) throws IOException; + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/event/EventQueue.java b/guacamole/client/src/net/sourceforge/guacamole/event/EventQueue.java new file mode 100644 index 000000000..a1d7a5246 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/event/EventQueue.java @@ -0,0 +1,191 @@ + +package net.sourceforge.guacamole.event; + +/* + * 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 . + */ + +import java.io.IOException; +import java.util.PriorityQueue; + +public class EventQueue { + + private int nextIndex = 0; + private final PriorityQueue queue = new PriorityQueue(); + private EventHandler handler; + + private final int deadline; + + private AutoflushThread autoflush = new AutoflushThread(); + + private class AutoflushThread extends Thread { + + private IOException error; + private long deadline; + private static final long ONE_YEAR = 31536000; + + private boolean killed = false; + + public AutoflushThread() { + this.deadline = ONE_YEAR; + start(); + } + + public void run() { + while (!killed) { + try { + if (deadline > 0) sleep(deadline); + dropPendingEvents(); + flush(); + } + catch (InterruptedException e) { + // Interrupt indicates event handled, or thread killed + if (killed) return; + } + catch (IOException e) { + error = e; + break; + } + } + } + + public void setDeadline(long deadline) { + this.deadline = deadline; + interrupt(); + } + + public void checkError() throws IOException { + if (error != null) throw error; + } + + public void kill() { + killed = true; + interrupt(); + } + + } + + public void close() { + autoflush.kill(); + } + + // Starts autoflush wait thread for any waiting events on the queue + private void startDeadlineAutoflush() { + synchronized (queue) { + + // No need to autoflush if nothing waiting + if (queue.size() == 0) return; + + // Get waiting event + E waiting = queue.peek(); + + if (waiting != null) { + long untilDeadline = deadline + waiting.getTime() - System.currentTimeMillis(); + + // Start autoflush thread which waits for time remaining until next + // event's deadline. + autoflush.setDeadline(untilDeadline); + } + else + autoflush.setDeadline(AutoflushThread.ONE_YEAR); + + } + } + + public EventQueue(EventHandler handler, int deadline) { + this.handler = handler; + this.deadline = deadline; + } + + public void add(E event) throws IOException { + synchronized (queue) { + + autoflush.checkError(); + + if (event.getIndex() < nextIndex) { + //System.err.println("Past event dropped."); + return; + } + + if (event == null) + throw new Error("Cannot add null event."); + + queue.add(event); + } + + flush(); + } + + private E next() { + synchronized (queue) { + // If no events, return nothing. + if (queue.size() == 0) return null; + + // If still waiting for true next event, return nothing. + E event = queue.peek(); + if (event.getIndex() != nextIndex) + return null; + + // If event found, expect next event, remove and return current. + queue.remove(); + nextIndex++; + return event; + } + } + + // Return number of waiting events + public int getWaiting() { + synchronized (queue) { + // If no events, then none waiting. + if (queue.size() == 0) return 0; + + // If we have the next event, then none waiting. + E event = queue.peek(); + if (event.getIndex() == nextIndex) + return 0; + + // Otherwise, all events are waiting. + return queue.size(); + } + } + + // Stop waiting for any unreceived events + private void dropPendingEvents() { + synchronized (queue) { + // If no events, nothing needs to be changed; + if (queue.size() == 0) return; + + // Otherwise, update nextIndex to index of next event + E event = queue.peek(); + nextIndex = event.getIndex(); + } + } + + // Attempts to flush queue + // If any events remain, an autoflush thread is started. + private void flush() throws IOException { + synchronized (queue) { + E nextEvent; + while ((nextEvent = next()) != null) + handler.handle(nextEvent); + } + + // Start autoflush thread for any remaining events. + startDeadlineAutoflush(); + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/event/KeyEvent.java b/guacamole/client/src/net/sourceforge/guacamole/event/KeyEvent.java new file mode 100644 index 000000000..a287dd448 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/event/KeyEvent.java @@ -0,0 +1,39 @@ +package net.sourceforge.guacamole.event; + +/* + * 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 . + */ + +public class KeyEvent extends Event { + + private int keysym; + private boolean pressed; + + public KeyEvent(int index, int keysym, boolean pressed) { + super(index); + this.keysym = keysym; + this.pressed = pressed; + } + + public int getKeySym() { + return keysym; + } + + public boolean getPressed() { + return pressed; + } +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/event/PointerEvent.java b/guacamole/client/src/net/sourceforge/guacamole/event/PointerEvent.java new file mode 100644 index 000000000..05692ada1 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/event/PointerEvent.java @@ -0,0 +1,69 @@ +package net.sourceforge.guacamole.event; + +/* + * 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 . + */ + +public class PointerEvent extends Event { + + private boolean leftButtonPressed; + private boolean middleButtonPressed; + private boolean rightButtonPressed; + private boolean upButtonPressed; + private boolean downButtonPressed; + private int x; + private int y; + + public PointerEvent(int index, boolean leftButtonPressed, boolean middleButtonPressed, boolean rightButtonPressed, boolean upButtonPressed, boolean downButtonPressed, int x, int y) { + super(index); + this.leftButtonPressed = leftButtonPressed; + this.middleButtonPressed = middleButtonPressed; + this.rightButtonPressed = rightButtonPressed; + this.upButtonPressed = upButtonPressed; + this.downButtonPressed = downButtonPressed; + this.x = x; + this.y = y; + } + + public boolean isLeftButtonPressed() { + return leftButtonPressed; + } + + public boolean isMiddleButtonPressed() { + return middleButtonPressed; + } + + public boolean isRightButtonPressed() { + return rightButtonPressed; + } + + public boolean isUpButtonPressed() { + return upButtonPressed; + } + + public boolean isDownButtonPressed() { + return downButtonPressed; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/Base64.java b/guacamole/client/src/net/sourceforge/guacamole/net/Base64.java new file mode 100644 index 000000000..1fa0676e0 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/Base64.java @@ -0,0 +1,66 @@ +package net.sourceforge.guacamole.net; + +/* + * 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 . + */ + +public class Base64 { + + private static String characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + + public static String toString(byte[] data) { + + StringBuffer buff = new StringBuffer(); + for (int i=0; i> 2 + )); + + buff.append(characters.charAt( + ((a & 0x03) << 4) | + ((b & 0xF0) >> 4) + )); + + if (i+1 < data.length) + buff.append(characters.charAt( + ((b & 0x0F) << 2) | + ((c & 0xC0) >> 6) + )); + else + buff.append('='); + + if (i+2 < data.length) + buff.append(characters.charAt( + (c & 0x3F) + )); + else + buff.append('='); + + } + + return buff.toString(); + + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/Configuration.java b/guacamole/client/src/net/sourceforge/guacamole/net/Configuration.java new file mode 100644 index 000000000..b8bc8d057 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/Configuration.java @@ -0,0 +1,130 @@ + +package net.sourceforge.guacamole.net; + +/* + * 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 . + */ + +import javax.servlet.ServletContext; +import net.sourceforge.guacamole.GuacamoleException; + +public abstract class Configuration { + + private ServletContext context; + + protected String humanReadableList(Object... values) { + + String list = ""; + for (int i=0; i= 1) + list += ", "; + + if (i == values.length -1) + list += " or "; + + list += "\"" + values[i] + "\""; + } + + return list; + + } + + protected String readParameter(String name, String defaultValue, String... allowedValues) throws GuacamoleException { + + String value = context.getInitParameter(name); + + // Use default if not specified + if (value == null) { + if (defaultValue == null) + throw new GuacamoleException("Parameter \"" + name + "\" is required."); + + return defaultValue; + } + + // If not restricted to certain values, just return whatever is given. + if (allowedValues.length == 0) + return value; + + // If restricted, only return value within given list + for (String allowedValue : allowedValues) + if (value.equals(allowedValue)) + return value; + + throw new GuacamoleException("Parameter \"" + name + "\" must be " + humanReadableList((Object) allowedValues)); + } + + protected boolean readBooleanParameter(String name, Boolean defaultValue) throws GuacamoleException { + + String value = context.getInitParameter(name); + + // Use default if not specified + if (value == null) { + if (defaultValue == null) + throw new GuacamoleException("Parameter \"" + name + "\" is required."); + + return defaultValue; + } + + value = value.trim(); + if (value.equals("true")) + return true; + + if (value.equals("false")) + return false; + + throw new GuacamoleException("Parameter \"" + name + "\" must be \"true\" or \"false\"."); + + } + + protected int readIntParameter(String name, Integer defaultValue, Integer... allowedValues) throws GuacamoleException { + + String parmString = context.getInitParameter(name); + + // Use default if not specified + if (parmString== null) { + if (defaultValue == null) + throw new GuacamoleException("Parameter \"" + name + "\" is required."); + + return defaultValue; + } + + try { + int value = Integer.parseInt(parmString); + + // If not restricted to certain values, just return whatever is given. + if (allowedValues.length == 0) + return value; + + // If restricted, only return value within given list + for (int allowedValue : allowedValues) + if (value == allowedValue) + return value; + + throw new GuacamoleException("Parameter \"" + name + "\" must be " + humanReadableList((Object) allowedValues)); + } + catch (NumberFormatException e) { + throw new GuacamoleException("Parameter \"" + name + "\" must be an integer.", e); + } + + } + + public Configuration(ServletContext context) { + this.context = context; + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java b/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java new file mode 100644 index 000000000..b357bb57c --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleConfiguration.java @@ -0,0 +1,46 @@ + +package net.sourceforge.guacamole.net; + +/* + * 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 . + */ + +import javax.servlet.ServletContext; +import net.sourceforge.guacamole.GuacamoleException; + +public class GuacamoleConfiguration extends Configuration { + + private String hostname; + private int port; + + public GuacamoleConfiguration(ServletContext context) throws GuacamoleException { + + super(context); + + hostname = context.getInitParameter("hostname"); + port = readIntParameter("port", null); + + } + + public int getPort() { + return port; + } + + public String getHostname() { + return hostname; + } +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java b/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java new file mode 100644 index 000000000..5c0a55dd9 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleServlet.java @@ -0,0 +1,62 @@ + +package net.sourceforge.guacamole.net; + +/* + * 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 . + */ + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.sourceforge.guacamole.GuacamoleException; + +public abstract class GuacamoleServlet extends HttpServlet { + + @Override + protected final void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + handleRequest(req, resp); + } + catch (GuacamoleException e) { + throw new ServletException(e); + } + } + + @Override + protected final void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + handleRequest(req, resp); + } + catch (GuacamoleException e) { + throw new ServletException(e); + } + } + + private final void handleRequest(HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + GuacamoleSession session = new GuacamoleSession(request.getSession(shouldCreateSession())); + handleRequest(session, request, response); + } + + protected abstract void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException; + + protected boolean shouldCreateSession() { + return false; + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleSession.java b/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleSession.java new file mode 100644 index 000000000..d00f4037a --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/GuacamoleSession.java @@ -0,0 +1,147 @@ + +package net.sourceforge.guacamole.net; + +/* + * 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 . + */ + +import java.util.concurrent.locks.ReentrantLock; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; +import net.sourceforge.guacamole.Client; +import net.sourceforge.guacamole.GuacamoleClient; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.event.KeyEvent; +import net.sourceforge.guacamole.event.PointerEvent; + +public class GuacamoleSession { + + private GuacamoleConfiguration config; + private final HttpSession session; + private Client client; + private ReentrantLock instructionStreamLock; + + private class SessionClient extends Client implements HttpSessionBindingListener { + + private Client client; + + public SessionClient(Client client) { + this.client = client; + } + + public void valueBound(HttpSessionBindingEvent event) { + // Do nothing + } + + public void valueUnbound(HttpSessionBindingEvent event) { + try { + disconnect(); + } + catch (GuacamoleException e) { + // Ignore + } + } + + public void write(char[] data, int off, int len) throws GuacamoleException { + client.write(data, off, len); + } + + public char[] read() throws GuacamoleException { + return client.read(); + } + + public void disconnect() throws GuacamoleException { + client.disconnect(); + } + + } + + public GuacamoleSession(HttpSession session) throws GuacamoleException { + + if (session == null) + throw new GuacamoleException("User has no session."); + + this.session = session; + synchronized (session) { + + // Read configuration parameters + ServletContext context = session.getServletContext(); + config = new GuacamoleConfiguration(context); + + client = (Client) session.getAttribute("CLIENT"); + instructionStreamLock = (ReentrantLock) session.getAttribute("INSTRUCTION_STREAM_LOCK"); + } + } + + public void connect() throws GuacamoleException { + synchronized (session) { + + if (client != null) + client.disconnect(); + + + client = new SessionClient( + new GuacamoleClient ( + config.getHostname(), + config.getPort() + ) + ); + + session.setAttribute("CLIENT", client); + + instructionStreamLock = new ReentrantLock(); + session.setAttribute("INSTRUCTION_STREAM_LOCK", instructionStreamLock); + + } + } + + public boolean isConnected() { + synchronized (session) { + return client != null; + } + } + + public GuacamoleConfiguration getConfiguration() { + return config; + } + + public Client getClient() { + synchronized (session) { + return client; + } + } + + public void invalidate() { + session.invalidate(); + } + + public void disconnect() throws GuacamoleException { + if (client != null) { + client.disconnect(); + + session.removeAttribute("CLIENT"); + client = null; + } + } + + public ReentrantLock getInstructionStreamLock() { + return instructionStreamLock; + } + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/XMLGuacamoleServlet.java b/guacamole/client/src/net/sourceforge/guacamole/net/XMLGuacamoleServlet.java new file mode 100644 index 000000000..2fef0c647 --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/XMLGuacamoleServlet.java @@ -0,0 +1,132 @@ + +package net.sourceforge.guacamole.net; + +/* + * 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 . + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import net.sourceforge.guacamole.Client; +import net.sourceforge.guacamole.GuacamoleException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public abstract class XMLGuacamoleServlet extends GuacamoleServlet { + + @Override + protected final void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + + response.setContentType("text/xml"); + response.setHeader("Cache-Control", "no-store"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + + try { + + // Create document + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.newDocument(); + + // Root element + Element root = document.createElement("guacamole"); + document.appendChild(root); + + try { + handleRequest(session, request, root); + } + catch (Throwable t) { + addFatalError(root, t.getMessage()); + + // FATAL error ... try to disconnect + if (session != null) { + Client client = session.getClient(); + try { + if (client != null) + client.disconnect(); + } + catch (GuacamoleException e) { + addFatalError(root, e.getMessage()); + } + } + } + + // Set up transformer + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + //transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + + // Write XML using transformer + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(bos); + DOMSource source = new DOMSource(document); + transformer.transform(source, result); + + + bos.flush(); + + byte[] xmlData = bos.toByteArray(); + response.setContentLength(xmlData.length); + OutputStream outputStream = response.getOutputStream(); + outputStream.write(xmlData); + + // Close stream + outputStream.close(); + } + catch (ParserConfigurationException e) { + throw new GuacamoleException(e); + } + catch (TransformerConfigurationException e) { + throw new GuacamoleException(e); + } + catch (TransformerException e) { + throw new GuacamoleException(e); + } + catch (IOException e) { + throw new GuacamoleException(e); + } + + + } + + private void addFatalError(Element root, String message) { + Element error = root.getOwnerDocument().createElement("error"); + error.setAttribute("type", "fatal"); + error.setTextContent(message); + root.appendChild(error); + } + + protected abstract void handleRequest(GuacamoleSession session, ServletRequest request, Element root) throws GuacamoleException; + +} diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/input/Inbound.java b/guacamole/client/src/net/sourceforge/guacamole/net/input/Inbound.java new file mode 100644 index 000000000..7be7bc6cb --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/input/Inbound.java @@ -0,0 +1,64 @@ +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 . + */ + +import javax.servlet.ServletRequest; +import net.sourceforge.guacamole.GuacamoleException; +import org.w3c.dom.Element; + +import java.io.Reader; +import java.io.IOException; + +import net.sourceforge.guacamole.net.GuacamoleSession; +import net.sourceforge.guacamole.net.XMLGuacamoleServlet; + +public class Inbound extends XMLGuacamoleServlet { + + protected boolean shouldCreateSession() { + return true; + } + + @Override + protected void handleRequest(GuacamoleSession session, ServletRequest request, Element root) throws GuacamoleException { + + if (!session.isConnected()) + session.connect(); + + // Send data + try { + + Reader input = request.getReader(); + char[] buffer = new char[8192]; + + int length; + while ((length = input.read(buffer, 0, buffer.length)) != -1) + session.getClient().write(buffer, 0, length); + + } + catch (IOException e) { + throw new GuacamoleException("I/O Error sending data to server: " + e.getMessage(), e); + } + catch (GuacamoleException e) { + throw new GuacamoleException("Error sending data to server: " + e.getMessage(), e); + } + + } + +} + diff --git a/guacamole/client/src/net/sourceforge/guacamole/net/output/InstructionStream.java b/guacamole/client/src/net/sourceforge/guacamole/net/output/InstructionStream.java new file mode 100644 index 000000000..27e5e65de --- /dev/null +++ b/guacamole/client/src/net/sourceforge/guacamole/net/output/InstructionStream.java @@ -0,0 +1,97 @@ +package net.sourceforge.guacamole.net.output; + +/* + * 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 . + */ + +import java.io.Writer; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.concurrent.locks.ReentrantLock; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import net.sourceforge.guacamole.Client; +import net.sourceforge.guacamole.net.GuacamoleServlet; +import net.sourceforge.guacamole.GuacamoleException; +import net.sourceforge.guacamole.net.GuacamoleSession; + + +public class InstructionStream extends GuacamoleServlet { + + @Override + protected void handleRequest(GuacamoleSession session, HttpServletRequest request, HttpServletResponse response) throws GuacamoleException { + + ReentrantLock instructionStreamLock = session.getInstructionStreamLock(); + instructionStreamLock.lock(); + + try { + + response.setContentType("text/plain"); + Writer out = response.getWriter(); + + try { + + // Query new update from server + Client client = session.getClient(); + + // For all messages, until another stream is ready (we send at least one message) + char[] message; + while ((message = client.read()) != null) { + + // Get message output bytes + out.write(message, 0, message.length); + out.flush(); + response.flushBuffer(); + + // No more messages another stream can take over + if (instructionStreamLock.hasQueuedThreads()) + break; + + } + + if (message == null) { + session.disconnect(); + throw new GuacamoleException("Disconnected."); + } + + } + catch (GuacamoleException e) { + out.write("error:" + e.getMessage() + ";"); + out.flush(); + response.flushBuffer(); + } + + // End-of-instructions marker + out.write(';'); + out.flush(); + response.flushBuffer(); + + } + catch (UnsupportedEncodingException e) { + throw new GuacamoleException("UTF-8 not supported by Java.", e); + } + catch (IOException e) { + throw new GuacamoleException("I/O error writing to servlet output stream.", e); + } + finally { + instructionStreamLock.unlock(); + } + + } + +} + diff --git a/guacamole/client/tunnel.php b/guacamole/client/tunnel.php deleted file mode 100644 index eb9b41ce7..000000000 --- a/guacamole/client/tunnel.php +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/guacamole/client/web/WEB-INF/web.xml b/guacamole/client/web/WEB-INF/web.xml new file mode 100644 index 000000000..c61e6b842 --- /dev/null +++ b/guacamole/client/web/WEB-INF/web.xml @@ -0,0 +1,68 @@ + + + + + + index.html + + + + 30 + + + + + Instruction stream servlet. + InstructionStream + net.sourceforge.guacamole.net.output.InstructionStream + + + InstructionStream + /instructions + + + Input servlet. + Inbound + net.sourceforge.guacamole.net.input.Inbound + + + Inbound + /inbound + + + Guacamole Access Restrictions + + Guacamole + All servlets/pages within Guacamole. + /* + + + Only allow Guacamole users access. + guacamole + + + + BASIC + Guacamole + + + Guacamole + guacamole + + diff --git a/guacamole/client/agpl-3.0-standalone.html b/guacamole/client/web/agpl-3.0-standalone.html similarity index 100% rename from guacamole/client/agpl-3.0-standalone.html rename to guacamole/client/web/agpl-3.0-standalone.html diff --git a/guacamole/client/guacamole.css b/guacamole/client/web/guacamole.css similarity index 100% rename from guacamole/client/guacamole.css rename to guacamole/client/web/guacamole.css diff --git a/guacamole/client/images/agpl-logo.png b/guacamole/client/web/images/agpl-logo.png similarity index 100% rename from guacamole/client/images/agpl-logo.png rename to guacamole/client/web/images/agpl-logo.png diff --git a/guacamole/client/images/checker.png b/guacamole/client/web/images/checker.png similarity index 100% rename from guacamole/client/images/checker.png rename to guacamole/client/web/images/checker.png diff --git a/guacamole/client/images/guacamole-64-icon.png b/guacamole/client/web/images/guacamole-64-icon.png similarity index 100% rename from guacamole/client/images/guacamole-64-icon.png rename to guacamole/client/web/images/guacamole-64-icon.png diff --git a/guacamole/client/images/guacamole-icon-64.png b/guacamole/client/web/images/guacamole-icon-64.png similarity index 100% rename from guacamole/client/images/guacamole-icon-64.png rename to guacamole/client/web/images/guacamole-icon-64.png diff --git a/guacamole/client/images/guacamole-logo.png b/guacamole/client/web/images/guacamole-logo.png similarity index 100% rename from guacamole/client/images/guacamole-logo.png rename to guacamole/client/web/images/guacamole-logo.png diff --git a/guacamole/client/images/mouse/blank.cur b/guacamole/client/web/images/mouse/blank.cur similarity index 100% rename from guacamole/client/images/mouse/blank.cur rename to guacamole/client/web/images/mouse/blank.cur diff --git a/guacamole/client/images/mouse/blank.gif b/guacamole/client/web/images/mouse/blank.gif similarity index 100% rename from guacamole/client/images/mouse/blank.gif rename to guacamole/client/web/images/mouse/blank.gif diff --git a/guacamole/client/images/mouse/dot.gif b/guacamole/client/web/images/mouse/dot.gif similarity index 100% rename from guacamole/client/images/mouse/dot.gif rename to guacamole/client/web/images/mouse/dot.gif diff --git a/guacamole/client/images/noguacamole-logo.png b/guacamole/client/web/images/noguacamole-logo.png similarity index 100% rename from guacamole/client/images/noguacamole-logo.png rename to guacamole/client/web/images/noguacamole-logo.png diff --git a/guacamole/client/images/noimage92.png b/guacamole/client/web/images/noimage92.png similarity index 100% rename from guacamole/client/images/noimage92.png rename to guacamole/client/web/images/noimage92.png diff --git a/guacamole/client/images/spinner92.gif b/guacamole/client/web/images/spinner92.gif similarity index 100% rename from guacamole/client/images/spinner92.gif rename to guacamole/client/web/images/spinner92.gif diff --git a/guacamole/client/index.html b/guacamole/client/web/index.html similarity index 100% rename from guacamole/client/index.html rename to guacamole/client/web/index.html diff --git a/guacamole/client/javascript/guacamole.js b/guacamole/client/web/javascript/guacamole.js similarity index 87% rename from guacamole/client/javascript/guacamole.js rename to guacamole/client/web/javascript/guacamole.js index 460c99270..f82fa58e6 100644 --- a/guacamole/client/javascript/guacamole.js +++ b/guacamole/client/web/javascript/guacamole.js @@ -186,9 +186,38 @@ function VNCClient(display) { // Add event to queue, restart send loop if finished. outputMessageBuffer += message; + if (sendingMessages == 0) + sendPendingMessages(); } + function sendPendingMessages() { + + if (outputMessageBuffer.length > 0) { + + sendingMessages = 1; + + var message_xmlhttprequest = new XMLHttpRequest(); + message_xmlhttprequest.open("POST", "inbound"); + message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + message_xmlhttprequest.setRequestHeader("Content-length", outputMessageBuffer.length); + + // Once response received, send next queued event. + message_xmlhttprequest.onreadystatechange = function() { + if (message_xmlhttprequest.readyState == 4) + sendPendingMessages(); + } + + message_xmlhttprequest.send(outputMessageBuffer); + outputMessageBuffer = ""; // Clear buffer + + } + else + sendingMessages = 0; + + } + + /*****************************************/ /*** Clipboard ***/ /*****************************************/ @@ -272,7 +301,7 @@ function VNCClient(display) { function parseResponse() { // Start next request as soon as possible - if (xmlhttprequest.readyState >= 2 && nextRequest == null && uuid) + if (xmlhttprequest.readyState >= 2 && nextRequest == null) nextRequest = makeRequest(); // Parse stream when data is received and when complete. @@ -349,19 +378,10 @@ function VNCClient(display) { function makeRequest() { - if (uuid) - outputMessageBuffer = "resume:" + uuid + ";" + outputMessageBuffer; - - outputMessageBuffer += "pause;"; - // Download self var xmlhttprequest = new XMLHttpRequest(); - xmlhttprequest.open("POST", "tunnel.php"); - xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xmlhttprequest.setRequestHeader("Content-length", outputMessageBuffer.length); - - xmlhttprequest.send(outputMessageBuffer); - outputMessageBuffer = ""; + xmlhttprequest.open("POST", "instructions"); + xmlhttprequest.send(null); return xmlhttprequest; @@ -418,14 +438,8 @@ function VNCClient(display) { } - var uuid = null; - var instructionHandlers = { - "uuid": function(parameters) { - uuid = parameters[0]; - }, - "error": function(parameters) { showError(unescapeGuacamoleString(parameters[0])); }, @@ -545,8 +559,16 @@ function VNCClient(display) { this.connect = function() { + var message = "connect;"; + setState(STATE_CONNECTING); - sendMessage("connect;"); + + // Send connect message (synchronously... as necessary until handoff is implemented) + var connect_xmlhttprequest = new XMLHttpRequest(); + connect_xmlhttprequest.open("POST", "inbound", false); + connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + connect_xmlhttprequest.setRequestHeader("Content-length", message.length); + connect_xmlhttprequest.send(message); // Start reading data setState(STATE_WAITING); @@ -561,9 +583,15 @@ function VNCClient(display) { if (currentState != STATE_DISCONNECTED && currentState != STATE_DISCONNECTING) { + var message = "disconnect;"; setState(STATE_DISCONNECTING); - sendMessage("disconnect;"); + // Send disconnect message (synchronously... as necessary until handoff is implemented) + var disconnect_xmlhttprequest = new XMLHttpRequest(); + disconnect_xmlhttprequest.open("POST", "inbound", false); + disconnect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + disconnect_xmlhttprequest.setRequestHeader("Content-length", message.length); + disconnect_xmlhttprequest.send(message); setState(STATE_DISCONNECTED); } diff --git a/guacamole/client/javascript/keyboard.js b/guacamole/client/web/javascript/keyboard.js similarity index 100% rename from guacamole/client/javascript/keyboard.js rename to guacamole/client/web/javascript/keyboard.js diff --git a/guacamole/client/javascript/keymap.js b/guacamole/client/web/javascript/keymap.js similarity index 100% rename from guacamole/client/javascript/keymap.js rename to guacamole/client/web/javascript/keymap.js diff --git a/guacamole/client/javascript/layer.js b/guacamole/client/web/javascript/layer.js similarity index 100% rename from guacamole/client/javascript/layer.js rename to guacamole/client/web/javascript/layer.js diff --git a/guacamole/client/javascript/message.js b/guacamole/client/web/javascript/message.js similarity index 100% rename from guacamole/client/javascript/message.js rename to guacamole/client/web/javascript/message.js diff --git a/guacamole/client/javascript/mouse.js b/guacamole/client/web/javascript/mouse.js similarity index 100% rename from guacamole/client/javascript/mouse.js rename to guacamole/client/web/javascript/mouse.js diff --git a/guacamole/client/javascript/oskeyboard.js b/guacamole/client/web/javascript/oskeyboard.js similarity index 100% rename from guacamole/client/javascript/oskeyboard.js rename to guacamole/client/web/javascript/oskeyboard.js diff --git a/guacamole/client/keyboard.css b/guacamole/client/web/keyboard.css similarity index 100% rename from guacamole/client/keyboard.css rename to guacamole/client/web/keyboard.css diff --git a/guacamole/client/layouts/en-us-qwerty.xml b/guacamole/client/web/layouts/en-us-qwerty.xml similarity index 100% rename from guacamole/client/layouts/en-us-qwerty.xml rename to guacamole/client/web/layouts/en-us-qwerty.xml