3 * Guacamole - Clientless Remote Desktop
4 * Copyright (C) 2010 Michael Jumper
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
19 function GuacamoleClient(display, tunnel) {
22 var STATE_CONNECTING = 1;
23 var STATE_WAITING = 2;
24 var STATE_CONNECTED = 3;
25 var STATE_DISCONNECTING = 4;
26 var STATE_DISCONNECTED = 5;
28 var currentState = STATE_IDLE;
29 var stateChangeHandler = null;
31 tunnel.setInstructionHandler(doInstruction);
33 // Display must be relatively positioned for mouse to be handled properly
34 display.style.position = "relative";
36 function setState(state) {
37 if (state != currentState) {
39 if (stateChangeHandler)
40 stateChangeHandler(currentState);
44 this.setOnStateChangeHandler = function(handler) {
45 stateChangeHandler = handler;
48 function isConnected() {
49 return currentState == STATE_CONNECTED
50 || currentState == STATE_WAITING;
53 var cursorImage = null;
54 var cursorHotspotX = 0;
55 var cursorHotspotY = 0;
57 // FIXME: Make object. Clean up.
65 function redrawCursor(x, y) {
67 // Hide hardware cursor
68 if (cursorHidden == 0) {
69 display.className += " guac-hide-cursor";
74 cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH);
77 cursorRectX = x - cursorHotspotX;
78 cursorRectY = y - cursorHotspotY;
79 cursorRectW = cursorImage.width;
80 cursorRectH = cursorImage.height;
83 cursor.drawImage(cursorRectX, cursorRectY, cursorImage);
86 this.sendKeyEvent = function(pressed, keysym) {
87 // Do not send requests if not connected
91 tunnel.sendMessage("key:" + keysym + "," + pressed + ";");
94 this.sendMouseState = function(mouseState) {
96 // Do not send requests if not connected
100 // Draw client-side cursor
101 if (cursorImage != null) {
110 if (mouseState.getLeft()) buttonMask |= 1;
111 if (mouseState.getMiddle()) buttonMask |= 2;
112 if (mouseState.getRight()) buttonMask |= 4;
113 if (mouseState.getUp()) buttonMask |= 8;
114 if (mouseState.getDown()) buttonMask |= 16;
117 tunnel.sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";");
120 this.setClipboard = function(data) {
122 // Do not send requests if not connected
126 tunnel.sendMessage("clipboard:" + tunnel.escapeGuacamoleString(data) + ";");
131 var nameHandler = null;
132 this.setNameHandler = function(handler) {
133 nameHandler = handler;
136 var errorHandler = null;
137 this.setErrorHandler = function(handler) {
138 errorHandler = handler;
141 var clipboardHandler = null;
142 this.setClipboardHandler = function(handler) {
143 clipboardHandler = handler;
147 var displayWidth = 0;
148 var displayHeight = 0;
150 var layers = new Array();
151 var buffers = new Array();
154 this.getLayers = function() {
158 function getLayer(index) {
160 // If negative index, use buffer
164 var buffer = buffers[index];
166 // Create buffer if necessary
167 if (buffer == null) {
168 buffer = new Layer(0, 0);
169 buffer.setAutosize(1);
170 buffers[index] = buffer;
176 // If non-negative, use visible layer
179 var layer = layers[index];
183 layer = new Layer(displayWidth, displayHeight);
184 layers[index] = layer;
186 // (Re)-add existing layers in order
187 for (var i=0; i<layers.length; i++) {
190 // If already present, remove
191 if (layers[i].parentNode === display)
192 display.removeChild(layers[i]);
195 display.appendChild(layers[i]);
199 // Add cursor layer last
200 if (cursor != null) {
201 if (cursor.parentNode === display)
202 display.removeChild(cursor);
203 display.appendChild(cursor);
209 layer.resize(displayWidth, displayHeight);
217 var instructionHandlers = {
219 "error": function(parameters) {
220 if (errorHandler) errorHandler(tunnel.unescapeGuacamoleString(parameters[0]));
223 "name": function(parameters) {
224 if (nameHandler) nameHandler(tunnel.unescapeGuacamoleString(parameters[0]));
227 "clipboard": function(parameters) {
228 if (clipboardHandler) clipboardHandler(tunnel.unescapeGuacamoleString(parameters[0]));
231 "size": function(parameters) {
233 displayWidth = parseInt(parameters[0]);
234 displayHeight = parseInt(parameters[1]);
236 // Update (set) display size
237 display.style.width = displayWidth + "px";
238 display.style.height = displayHeight + "px";
240 // Set cursor layer width/height
242 cursor.resize(displayWidth, displayHeight);
246 "png": function(parameters) {
248 var layer = parseInt(parameters[0]);
249 var x = parseInt(parameters[1]);
250 var y = parseInt(parameters[2]);
251 var data = parameters[3];
253 getLayer(layer).draw(
256 "data:image/png;base64," + data
259 // If received first update, no longer waiting.
260 if (currentState == STATE_WAITING)
261 setState(STATE_CONNECTED);
265 "copy": function(parameters) {
267 var srcL = parseInt(parameters[0]);
268 var srcX = parseInt(parameters[1]);
269 var srcY = parseInt(parameters[2]);
270 var srcWidth = parseInt(parameters[3]);
271 var srcHeight = parseInt(parameters[4]);
272 var dstL = parseInt(parameters[5]);
273 var dstX = parseInt(parameters[6]);
274 var dstY = parseInt(parameters[7]);
276 getLayer(dstL).copyRect(
288 "cursor": function(parameters) {
290 var x = parseInt(parameters[0]);
291 var y = parseInt(parameters[1]);
292 var data = parameters[2];
294 if (cursor == null) {
295 cursor = new Layer(displayWidth, displayHeight);
296 display.appendChild(cursor);
299 // Start cursor image load
300 var image = new Image();
301 image.onload = function() {
305 redrawCursor(cursorRectX, cursorRectY);
307 image.src = "data:image/png;base64," + data
314 function doInstruction(opcode, parameters) {
316 var handler = instructionHandlers[opcode];
323 this.connect = function() {
325 setState(STATE_CONNECTING);
327 setState(STATE_WAITING);
332 function disconnect() {
334 // Only attempt disconnection not disconnected.
335 if (currentState != STATE_DISCONNECTED
336 && currentState != STATE_DISCONNECTING) {
338 setState(STATE_DISCONNECTING);
339 tunnel.sendMessage("disconnect;");
340 setState(STATE_DISCONNECTED);
345 this.disconnect = disconnect;