diff --git a/guacamole-common-js/src/main/resources/guacamole.js b/guacamole-common-js/src/main/resources/guacamole.js index 8aaef9612..a4ce995c7 100644 --- a/guacamole-common-js/src/main/resources/guacamole.js +++ b/guacamole-common-js/src/main/resources/guacamole.js @@ -14,14 +14,9 @@ * 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 . */ -function GuacamoleClient(display, tunnelURL) { - - var TUNNEL_CONNECT = tunnelURL + "?connect"; - var TUNNEL_READ = tunnelURL + "?read"; - var TUNNEL_WRITE = tunnelURL + "?write"; +function GuacamoleClient(display, tunnel) { var STATE_IDLE = 0; var STATE_CONNECTING = 1; @@ -32,7 +27,8 @@ function GuacamoleClient(display, tunnelURL) { var currentState = STATE_IDLE; var stateChangeHandler = null; - var pollResponse = 1; // Default to polling - will be turned off automatically if not needed + + tunnel.setInstructionHandler(doInstruction); // Display must be relatively positioned for mouse to be handled properly display.style.position = "relative"; @@ -66,7 +62,7 @@ function GuacamoleClient(display, tunnelURL) { var cursorHidden = 0; - function redrawCursor() { + function redrawCursor(x, y) { // Hide hardware cursor if (cursorHidden == 0) { @@ -78,8 +74,8 @@ function GuacamoleClient(display, tunnelURL) { cursor.clearRect(cursorRectX, cursorRectY, cursorRectW, cursorRectH); // Update rect - cursorRectX = mouse.getX() - cursorHotspotX; - cursorRectY = mouse.getY() - cursorHotspotY; + cursorRectX = x - cursorHotspotX; + cursorRectY = y - cursorHotspotY; cursorRectW = cursorImage.width; cursorRectH = cursorImage.height; @@ -87,90 +83,28 @@ function GuacamoleClient(display, tunnelURL) { cursor.drawImage(cursorRectX, cursorRectY, cursorImage); } - - - - /*****************************************/ - /*** Keyboard ***/ - /*****************************************/ - - var keyboard = new GuacamoleKeyboard(document); - - this.disableKeyboard = function() { - keyboard.setKeyPressedHandler(null); - keyboard.setKeyReleasedHandler(null); - }; - - this.enableKeyboard = function() { - keyboard.setKeyPressedHandler( - function (keysym) { - sendKeyEvent(1, keysym); - } - ); - - keyboard.setKeyReleasedHandler( - function (keysym) { - sendKeyEvent(0, keysym); - } - ); - }; - - // Enable keyboard by default - this.enableKeyboard(); - - function sendKeyEvent(pressed, keysym) { + this.sendKeyEvent = function(pressed, keysym) { // Do not send requests if not connected if (!isConnected()) return; - sendMessage("key:" + keysym + "," + pressed + ";"); + tunnel.sendMessage("key:" + keysym + "," + pressed + ";"); } - this.pressKey = function(keysym) { - sendKeyEvent(1, keysym); - }; - - this.releaseKey = function(keysym) { - sendKeyEvent(0, keysym); - }; - - - /*****************************************/ - /*** Mouse ***/ - /*****************************************/ - - var mouse = new GuacamoleMouse(display); - mouse.setButtonPressedHandler( - function(mouseState) { - sendMouseState(mouseState); - } - ); - - mouse.setButtonReleasedHandler( - function(mouseState) { - sendMouseState(mouseState); - } - ); - - mouse.setMovementHandler( - function(mouseState) { - - // Draw client-side cursor - if (cursorImage != null) { - redrawCursor(); - } - - sendMouseState(mouseState); - } - ); - - - function sendMouseState(mouseState) { + this.sendMouseState = function(mouseState) { // Do not send requests if not connected if (!isConnected()) return; + // Draw client-side cursor + if (cursorImage != null) { + redrawCursor( + mouseState.getX(), + mouseState.getY() + ); + } + // Build mask var buttonMask = 0; if (mouseState.getLeft()) buttonMask |= 1; @@ -180,80 +114,19 @@ function GuacamoleClient(display, tunnelURL) { if (mouseState.getDown()) buttonMask |= 16; // Send message - sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";"); + tunnel.sendMessage("mouse:" + mouseState.getX() + "," + mouseState.getY() + "," + buttonMask + ";"); } - var sendingMessages = 0; - var outputMessageBuffer = ""; - - function sendMessage(message) { - - // 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", TUNNEL_WRITE); - 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 ***/ - /*****************************************/ - this.setClipboard = function(data) { // Do not send requests if not connected if (!isConnected()) return; - sendMessage("clipboard:" + escapeGuacamoleString(data) + ";"); + tunnel.sendMessage("clipboard:" + tunnel.escapeGuacamoleString(data) + ";"); } - - function desaturateFilter(data, width, height) { - - for (var i=0; i= 2 && nextRequest == null) - nextRequest = makeRequest(); - - // Parse stream when data is received and when complete. - if (xmlhttprequest.readyState == 3 || - xmlhttprequest.readyState == 4) { - - // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data) - if (pollResponse == 1) { - if (xmlhttprequest.readyState == 3 && interval == null) - interval = setInterval(parseResponse, 30); - else if (xmlhttprequest.readyState == 4 && interval != null) - clearInterval(interval); - } - - // Halt on error during request - if (xmlhttprequest.status == 0) { - showError("Request canceled by browser."); - return; - } - else if (xmlhttprequest.status != 200) { - showError("Error during request (HTTP " + xmlhttprequest.status + "): " + xmlhttprequest.statusText); - return; - } - - var current = xmlhttprequest.responseText; - var instructionEnd; - - while ((instructionEnd = current.indexOf(";", startIndex)) != -1) { - - // Start next search at next instruction - startIndex = instructionEnd+1; - - var instruction = current.substr(instructionStart, - instructionEnd - instructionStart); - - instructionStart = startIndex; - - var opcodeEnd = instruction.indexOf(":"); - - var opcode; - var parameters; - if (opcodeEnd == -1) { - opcode = instruction; - parameters = new Array(); - } - else { - opcode = instruction.substr(0, opcodeEnd); - parameters = instruction.substr(opcodeEnd+1).split(","); - } - - // If we're done parsing, handle the next response. - if (opcode.length == 0) { - - if (isConnected()) { - delete xmlhttprequest; - if (nextRequest) - handleResponse(nextRequest); - } - - break; - } - - // Call instruction handler. - doInstruction(opcode, parameters); - } - - // Start search at end of string. - startIndex = current.length; - - delete instruction; - delete parameters; - - } - - } - - // If response polling enabled, attempt to detect if still - // necessary (via wrapping parseResponse()) - if (pollResponse == 1) { - xmlhttprequest.onreadystatechange = function() { - - // If we receive two or more readyState==3 events, - // there is no need to poll. - if (xmlhttprequest.readyState == 3) { - dataUpdateEvents++; - if (dataUpdateEvents >= 2) { - pollResponse = 0; - xmlhttprequest.onreadystatechange = parseResponse; - } - } - - parseResponse(); - } - } - - // Otherwise, just parse - else - xmlhttprequest.onreadystatechange = parseResponse; - - parseResponse(); - - } - - - function makeRequest() { - - // Download self - var xmlhttprequest = new XMLHttpRequest(); - xmlhttprequest.open("POST", TUNNEL_READ); - xmlhttprequest.send(null); - - return xmlhttprequest; - - } - - function escapeGuacamoleString(str) { - - var escapedString = ""; - - for (var i=0; i 0) { + + sendingMessages = 1; + + var message_xmlhttprequest = new XMLHttpRequest(); + message_xmlhttprequest.open("POST", TUNNEL_WRITE); + 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; + + } + + + function handleResponse(xmlhttprequest) { + + var interval = null; + var nextRequest = null; + + var dataUpdateEvents = 0; + var instructionStart = 0; + var startIndex = 0; + + function parseResponse() { + + // Start next request as soon as possible + if (xmlhttprequest.readyState >= 2 && nextRequest == null) + nextRequest = makeRequest(); + + // Parse stream when data is received and when complete. + if (xmlhttprequest.readyState == 3 || + xmlhttprequest.readyState == 4) { + + // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data) + if (pollResponse == 1) { + if (xmlhttprequest.readyState == 3 && interval == null) + interval = setInterval(parseResponse, 30); + else if (xmlhttprequest.readyState == 4 && interval != null) + clearInterval(interval); + } + + // Halt on error during request + if (xmlhttprequest.status == 0) { + showError("Request canceled by browser."); + return; + } + else if (xmlhttprequest.status != 200) { + showError("Error during request (HTTP " + xmlhttprequest.status + "): " + xmlhttprequest.statusText); + return; + } + + var current = xmlhttprequest.responseText; + var instructionEnd; + + while ((instructionEnd = current.indexOf(";", startIndex)) != -1) { + + // Start next search at next instruction + startIndex = instructionEnd+1; + + var instruction = current.substr(instructionStart, + instructionEnd - instructionStart); + + instructionStart = startIndex; + + var opcodeEnd = instruction.indexOf(":"); + + var opcode; + var parameters; + if (opcodeEnd == -1) { + opcode = instruction; + parameters = new Array(); + } + else { + opcode = instruction.substr(0, opcodeEnd); + parameters = instruction.substr(opcodeEnd+1).split(","); + } + + // If we're done parsing, handle the next response. + if (opcode.length == 0) { + + delete xmlhttprequest; + if (nextRequest) + handleResponse(nextRequest); + + break; + } + + // Call instruction handler. + if (instructionHandler != null) + instructionHandler(opcode, parameters); + } + + // Start search at end of string. + startIndex = current.length; + + delete instruction; + delete parameters; + + } + + } + + // If response polling enabled, attempt to detect if still + // necessary (via wrapping parseResponse()) + if (pollResponse == 1) { + xmlhttprequest.onreadystatechange = function() { + + // If we receive two or more readyState==3 events, + // there is no need to poll. + if (xmlhttprequest.readyState == 3) { + dataUpdateEvents++; + if (dataUpdateEvents >= 2) { + pollResponse = 0; + xmlhttprequest.onreadystatechange = parseResponse; + } + } + + parseResponse(); + } + } + + // Otherwise, just parse + else + xmlhttprequest.onreadystatechange = parseResponse; + + parseResponse(); + + } + + + function makeRequest() { + + // Download self + var xmlhttprequest = new XMLHttpRequest(); + xmlhttprequest.open("POST", TUNNEL_READ); + xmlhttprequest.send(null); + + return xmlhttprequest; + + } + + function connect() { + + // Start tunnel and connect synchronously + var connect_xmlhttprequest = new XMLHttpRequest(); + connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, false); + connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + connect_xmlhttprequest.setRequestHeader("Content-length", 0); + connect_xmlhttprequest.send(null); + + // Start reading data + handleResponse(makeRequest()); + + }; + + // External API + this.connect = connect; + this.sendMessage = sendMessage; + this.setInstructionHandler = function(handler) { + instructionHandler = handler; + } + + this.escapeGuacamoleString = function(str) { + + var escapedString = ""; + + for (var i=0; i