diff --git a/guacamole-common-js/src/main/webapp/modules/Client.js b/guacamole-common-js/src/main/webapp/modules/Client.js index 4459c8f7a..f34eb53d6 100644 --- a/guacamole-common-js/src/main/webapp/modules/Client.js +++ b/guacamole-common-js/src/main/webapp/modules/Client.js @@ -23,9 +23,9 @@ var Guacamole = Guacamole || {}; /** - * Guacamole protocol client. Given a display element and {@link Guacamole.Tunnel}, + * Guacamole protocol client. Given a {@link Guacamole.Tunnel}, * automatically handles incoming and outgoing Guacamole instructions via the - * provided tunnel, updating the display using one or more canvas elements. + * provided tunnel, updating its display using one or more canvas elements. * * @constructor * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive @@ -47,10 +47,6 @@ Guacamole.Client = function(tunnel) { var currentTimestamp = 0; var pingInterval = null; - var displayWidth = 0; - var displayHeight = 0; - var displayScale = 1; - /** * Translation from Guacamole protocol line caps to Layer line caps. * @private @@ -71,62 +67,16 @@ Guacamole.Client = function(tunnel) { 2: "round" }; - // Create bounding div - var bounds = document.createElement("div"); - bounds.style.position = "relative"; - bounds.style.width = (displayWidth*displayScale) + "px"; - bounds.style.height = (displayHeight*displayScale) + "px"; - - // Create display - var display = document.createElement("div"); - display.style.position = "relative"; - display.style.width = displayWidth + "px"; - display.style.height = displayHeight + "px"; - - // Ensure transformations on display originate at 0,0 - display.style.transformOrigin = - display.style.webkitTransformOrigin = - display.style.MozTransformOrigin = - display.style.OTransformOrigin = - display.style.msTransformOrigin = - "0 0"; - - // Create default layer - var default_layer_container = new Guacamole.Client.LayerContainer(0, displayWidth, displayHeight); - - // Position default layer - var default_layer_container_element = default_layer_container.getElement(); - default_layer_container_element.style.position = "absolute"; - default_layer_container_element.style.left = "0px"; - default_layer_container_element.style.top = "0px"; - default_layer_container_element.style.overflow = "hidden"; - default_layer_container_element.style.zIndex = "0"; - - // Create cursor layer - var cursor = new Guacamole.Client.LayerContainer(null, 0, 0); - cursor.getLayer().setChannelMask(Guacamole.Layer.SRC); - cursor.getLayer().autoflush = true; - - // Position cursor layer - var cursor_element = cursor.getElement(); - cursor_element.style.position = "absolute"; - cursor_element.style.left = "0px"; - cursor_element.style.top = "0px"; - cursor_element.style.zIndex = "1"; - - // Add default layer and cursor to display - display.appendChild(default_layer_container.getElement()); - display.appendChild(cursor.getElement()); - - // Add display to bounds - bounds.appendChild(display); - - // Initially, only default layer exists - var layers = [default_layer_container]; - - // No initial buffers - var buffers = []; + /** + * The underlying Guacamole display. + */ + var display = new Guacamole.Display(); + /** + * All available layers and buffers + */ + var layers = {}; + // No initial parsers var parsers = []; @@ -155,33 +105,16 @@ Guacamole.Client = function(tunnel) { || currentState == STATE_WAITING; } - var cursorHotspotX = 0; - var cursorHotspotY = 0; - - var cursorX = 0; - var cursorY = 0; - - function moveCursor(x, y) { - - // Move cursor layer - cursor.translate(x - cursorHotspotX, y - cursorHotspotY); - - // Update stored position - cursorX = x; - cursorY = y; - - } - /** - * Returns an element containing the display of this Guacamole.Client. - * Adding the element returned by this function to an element in the body - * of a document will cause the client's display to be visible. + * Returns the underlying display of this Guacamole.Client. The display + * contains an Element which can be added to the DOM, causing the + * display to become visible. * - * @return {Element} An element containing ths display of this - * Guacamole.Client. + * @return {Guacamole.Display} The underlying display of this + * Guacamole.Client. */ this.getDisplay = function() { - return bounds; + return display; }; /** @@ -230,7 +163,7 @@ Guacamole.Client = function(tunnel) { return; // Update client-side cursor - moveCursor( + display.moveCursor( Math.floor(mouseState.x), Math.floor(mouseState.y) ); @@ -443,16 +376,6 @@ Guacamole.Client = function(tunnel) { */ this.onclipboard = null; - /** - * Fired when the default layer (and thus the entire Guacamole display) - * is resized. - * - * @event - * @param {Number} width The new width of the Guacamole display. - * @param {Number} height The new height of the Guacamole display. - */ - this.onresize = null; - /** * Fired when a file stream is created. The stream provided to this event * handler will contain its own event handlers for received data. @@ -488,60 +411,37 @@ Guacamole.Client = function(tunnel) { */ this.onsync = null; - // Layers - function getBufferLayer(index) { - - index = -1 - index; - var buffer = buffers[index]; - - // Create buffer if necessary - if (buffer == null) { - buffer = new Guacamole.Layer(0, 0); - buffer.autoflush = 1; - buffer.autosize = 1; - buffers[index] = buffer; - } - - return buffer; - - } - - function getLayerContainer(index) { + /** + * Returns the layer with the given index, creating it if necessary. + * Positive indices refer to visible layers, an index of zero refers to + * the default layer, and negative indices refer to buffers. + * + * @param {Number} index The index of the layer to retrieve. + * @return {Guacamole.Display.VisibleLayer|Guacamole.Layer} The layer having the given index. + */ + function getLayer(index) { + // Get layer, create if necessary var layer = layers[index]; - if (layer == null) { + if (!layer) { + // Create layer based on index + if (index === 0) + layer = display.getDefaultLayer(); + else if (index > 0) + layer = display.createLayer(); + else + layer = display.createBuffer(); + // Add new layer - layer = new Guacamole.Client.LayerContainer(index, displayWidth, displayHeight); layers[index] = layer; - // Get and position layer - var layer_element = layer.getElement(); - layer_element.style.position = "absolute"; - layer_element.style.left = "0px"; - layer_element.style.top = "0px"; - layer_element.style.overflow = "hidden"; - - // Add to default layer container - layer.move(default_layer_container, 0, 0, 0); - } return layer; } - function getLayer(index) { - - // If buffer, just get layer - if (index < 0) - return getBufferLayer(index); - - // Otherwise, retrieve layer from layer container - return getLayerContainer(index).getLayer(); - - } - function getParser(index) { var parser = parsers[index]; @@ -576,7 +476,7 @@ Guacamole.Client = function(tunnel) { var layerPropertyHandlers = { "miter-limit": function(layer, value) { - layer.setMiterLimit(parseFloat(value)); + display.setMiterLimit(layer, parseFloat(value)); } }; @@ -622,7 +522,7 @@ Guacamole.Client = function(tunnel) { var endAngle = parseFloat(parameters[5]); var negative = parseInt(parameters[6]); - layer.arc(x, y, radius, startAngle, endAngle, negative != 0); + display.arc(layer, x, y, radius, startAngle, endAngle, negative != 0); }, @@ -671,9 +571,8 @@ Guacamole.Client = function(tunnel) { var b = parseInt(parameters[4]); var a = parseInt(parameters[5]); - layer.setChannelMask(channelMask); - - layer.fillColor(r, g, b, a); + display.setChannelMask(layer, channelMask); + display.fillColor(layer, r, g, b, a); }, @@ -681,7 +580,7 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[0])); - layer.clip(); + display.clip(layer); }, @@ -706,7 +605,7 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[0])); - layer.close(); + display.close(layer); }, @@ -722,17 +621,9 @@ Guacamole.Client = function(tunnel) { var dstX = parseInt(parameters[7]); var dstY = parseInt(parameters[8]); - dstL.setChannelMask(channelMask); - - dstL.copy( - srcL, - srcX, - srcY, - srcWidth, - srcHeight, - dstX, - dstY - ); + display.setChannelMask(dstL, channelMask); + display.copy(srcL, srcX, srcY, srcWidth, srcHeight, + dstL, dstX, dstY); }, @@ -748,38 +639,23 @@ Guacamole.Client = function(tunnel) { var b = parseInt(parameters[7]); var a = parseInt(parameters[8]); - layer.setChannelMask(channelMask); - - layer.strokeColor(cap, join, thickness, r, g, b, a); + display.setChannelMask(layer, channelMask); + display.strokeColor(layer, cap, join, thickness, r, g, b, a); }, "cursor": function(parameters) { - cursorHotspotX = parseInt(parameters[0]); - cursorHotspotY = parseInt(parameters[1]); + var cursorHotspotX = parseInt(parameters[0]); + var cursorHotspotY = parseInt(parameters[1]); var srcL = getLayer(parseInt(parameters[2])); var srcX = parseInt(parameters[3]); var srcY = parseInt(parameters[4]); var srcWidth = parseInt(parameters[5]); var srcHeight = parseInt(parameters[6]); - // Reset cursor size - cursor.resize(srcWidth, srcHeight); - - // Draw cursor to cursor layer - cursor.getLayer().copy( - srcL, - srcX, - srcY, - srcWidth, - srcHeight, - 0, - 0 - ); - - // Update cursor position (hotspot may have changed) - moveCursor(cursorX, cursorY); + display.setCursor(cursorHotspotX, cursorHotspotY, + srcL, srcX, srcY, srcWidth, srcHeight); }, @@ -793,7 +669,7 @@ Guacamole.Client = function(tunnel) { var x = parseInt(parameters[5]); var y = parseInt(parameters[6]); - layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y); + display.curveTo(layer, cp1x, cp1y, cp2x, cp2y, x, y); }, @@ -805,8 +681,8 @@ Guacamole.Client = function(tunnel) { if (layer_index > 0) { // Remove from parent - var layer_container = getLayerContainer(layer_index); - layer_container.dispose(); + var layer = getLayer(layer_index); + layer.dispose(); // Delete reference delete layers[layer_index]; @@ -815,7 +691,7 @@ Guacamole.Client = function(tunnel) { // If buffer, just delete reference else if (layer_index < 0) - delete buffers[-1 - layer_index]; + delete layers[layer_index]; // Attempting to dispose the root layer currently has no effect. @@ -833,12 +709,9 @@ Guacamole.Client = function(tunnel) { // Only valid for visible layers (not buffers) if (layer_index >= 0) { - - // Set layer transform - var layer_container = getLayerContainer(layer_index); - layer_container.transform(a, b, c, d, e, f); - - } + var layer = getLayer(layer_index); + layer.distort(a, b, c, d, e, f); + } }, @@ -889,7 +762,7 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[0])); - layer.setTransform(1, 0, 0, 1, 0, 0); + display.setTransform(layer, 1, 0, 0, 1, 0, 0); }, @@ -899,9 +772,8 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[1])); var srcLayer = getLayer(parseInt(parameters[2])); - layer.setChannelMask(channelMask); - - layer.fillLayer(srcLayer); + display.setChannelMask(layer, channelMask); + display.fillLayer(layer, srcLayer); }, @@ -911,7 +783,7 @@ Guacamole.Client = function(tunnel) { var x = parseInt(parameters[1]); var y = parseInt(parameters[2]); - layer.lineTo(x, y); + display.lineTo(layer, x, y); }, @@ -921,9 +793,8 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[1])); var srcLayer = getLayer(parseInt(parameters[2])); - layer.setChannelMask(channelMask); - - layer.strokeLayer(srcLayer); + display.setChannelMask(layer, channelMask); + display.strokeLayer(layer, srcLayer); }, @@ -937,14 +808,9 @@ Guacamole.Client = function(tunnel) { // Only valid for non-default layers if (layer_index > 0 && parent_index >= 0) { - - // Get container element - var layer_container = getLayerContainer(layer_index); - var parent = getLayerContainer(parent_index); - - // Move layer - layer_container.move(parent, x, y, z); - + var layer = getLayer(layer_index); + var parent = getLayer(parent_index); + layer.move(parent, x, y, z); } }, @@ -984,13 +850,8 @@ Guacamole.Client = function(tunnel) { var y = parseInt(parameters[3]); var data = parameters[4]; - layer.setChannelMask(channelMask); - - layer.draw( - x, - y, - "data:image/png;base64," + data - ); + display.setChannelMask(layer, channelMask); + display.draw(layer, x, y, "data:image/png;base64," + data); }, @@ -998,7 +859,7 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[0])); - layer.pop(); + display.pop(layer); }, @@ -1006,7 +867,7 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[0])); - layer.push(); + display.push(layer); }, @@ -1018,7 +879,7 @@ Guacamole.Client = function(tunnel) { var w = parseInt(parameters[3]); var h = parseInt(parameters[4]); - layer.rect(x, y, w, h); + display.rect(layer, x, y, w, h); }, @@ -1026,7 +887,7 @@ Guacamole.Client = function(tunnel) { var layer = getLayer(parseInt(parameters[0])); - layer.reset(); + display.reset(layer); }, @@ -1050,8 +911,8 @@ Guacamole.Client = function(tunnel) { // Only valid for visible layers (not buffers) if (layer_index >= 0) { - var layer_container = getLayerContainer(layer_index); - layer_container.shade(a); + var layer = getLayer(layer_index); + layer.shade(a); } }, @@ -1059,43 +920,11 @@ Guacamole.Client = function(tunnel) { "size": function(parameters) { var layer_index = parseInt(parameters[0]); + var layer = getLayer(layer_index); var width = parseInt(parameters[1]); var height = parseInt(parameters[2]); - // If not buffer, resize layer and container - if (layer_index >= 0) { - - // Resize layer - var layer_container = getLayerContainer(layer_index); - layer_container.resize(width, height); - - // If layer is default, resize display - if (layer_index == 0) { - - displayWidth = width; - displayHeight = height; - - // Update (set) display size - display.style.width = displayWidth + "px"; - display.style.height = displayHeight + "px"; - - // Update bounds size - bounds.style.width = (displayWidth*displayScale) + "px"; - bounds.style.height = (displayHeight*displayScale) + "px"; - - // Call resize event handler if defined - if (guac_client.onresize) - guac_client.onresize(width, height); - - } - - } - - // If buffer, resize layer only - else { - var layer = getBufferLayer(parseInt(parameters[0])); - layer.resize(width, height); - } + display.resize(layer, width, height); }, @@ -1111,61 +940,18 @@ Guacamole.Client = function(tunnel) { "sync": function(parameters) { - var timestamp = parameters[0]; + var timestamp = parseInt(parameters[0]); - // When all layers have finished rendering all instructions - // UP TO THIS POINT IN TIME, send sync response. - - var layersToSync = 0; - function syncLayer() { - - layersToSync--; - - // Send sync response when layers are finished - if (layersToSync == 0) { - if (timestamp != currentTimestamp) { - tunnel.sendMessage("sync", timestamp); - currentTimestamp = timestamp; - } - } - - } - - // Count active, not-ready layers and install sync tracking hooks - for (var i=0; i 0 && layer.height > 0) { - - // Save and update alpha - var initial_alpha = context.globalAlpha; - context.globalAlpha *= layer.alpha / 255.0; - - // Copy data - context.drawImage(layer.getLayer().getCanvas(), x, y); - - // Draw all children - var children = get_children(layer); - for (var i=0; i 0 && layer.height > 0) { + + // Save and update alpha + var initial_alpha = context.globalAlpha; + context.globalAlpha *= layer.alpha / 255.0; + + // Copy data + context.drawImage(layer.getCanvas(), x, y); + + // Draw all children + var children = get_children(layer); + for (var i=0; i= srcCanvas.width || srcy >= srcCanvas.height) return; + // If entire rectangle outside source canvas, stop + if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; - // Otherwise, clip rectangle to area - if (srcx + srcw > srcCanvas.width) - srcw = srcCanvas.width - srcx; + // Otherwise, clip rectangle to area + if (srcx + srcw > srcCanvas.width) + srcw = srcCanvas.width - srcx; - if (srcy + srch > srcCanvas.height) - srch = srcCanvas.height - srcy; + if (srcy + srch > srcCanvas.height) + srch = srcCanvas.height - srcy; - // Stop if nothing to draw. - if (srcw == 0 || srch == 0) return; + // Stop if nothing to draw. + if (srcw === 0 || srch === 0) return; - if (layer.autosize != 0) fitRect(x, y, srcw, srch); + if (layer.autosize) fitRect(x, y, srcw, srch); - // Get image data from src and dst - var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); - var dst = displayContext.getImageData(x , y, srcw, srch); + // Get image data from src and dst + var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); + var dst = context.getImageData(x , y, srcw, srch); - // Apply transfer for each pixel - for (var i=0; i= srcCanvas.width || srcy >= srcCanvas.height) return; + // If entire rectangle outside source canvas, stop + if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; - // Otherwise, clip rectangle to area - if (srcx + srcw > srcCanvas.width) - srcw = srcCanvas.width - srcx; + // Otherwise, clip rectangle to area + if (srcx + srcw > srcCanvas.width) + srcw = srcCanvas.width - srcx; - if (srcy + srch > srcCanvas.height) - srch = srcCanvas.height - srcy; + if (srcy + srch > srcCanvas.height) + srch = srcCanvas.height - srcy; - // Stop if nothing to draw. - if (srcw == 0 || srch == 0) return; + // Stop if nothing to draw. + if (srcw === 0 || srch === 0) return; - if (layer.autosize != 0) fitRect(x, y, srcw, srch); + if (layer.autosize) fitRect(x, y, srcw, srch); - // Get image data from src and dst - var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); - displayContext.putImageData(src, x, y); + // Get image data from src and dst + var src = srcLayer.getCanvas().getContext("2d").getImageData(srcx, srcy, srcw, srch); + context.putImageData(src, x, y); - }); }; /** @@ -694,27 +395,25 @@ Guacamole.Layer = function(width, height) { * @param {Number} y The destination Y coordinate. */ this.copy = function(srcLayer, srcx, srcy, srcw, srch, x, y) { - scheduleTaskSynced(srcLayer, function() { - var srcCanvas = srcLayer.getCanvas(); + var srcCanvas = srcLayer.getCanvas(); - // If entire rectangle outside source canvas, stop - if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; + // If entire rectangle outside source canvas, stop + if (srcx >= srcCanvas.width || srcy >= srcCanvas.height) return; - // Otherwise, clip rectangle to area - if (srcx + srcw > srcCanvas.width) - srcw = srcCanvas.width - srcx; + // Otherwise, clip rectangle to area + if (srcx + srcw > srcCanvas.width) + srcw = srcCanvas.width - srcx; - if (srcy + srch > srcCanvas.height) - srch = srcCanvas.height - srcy; + if (srcy + srch > srcCanvas.height) + srch = srcCanvas.height - srcy; - // Stop if nothing to draw. - if (srcw == 0 || srch == 0) return; + // Stop if nothing to draw. + if (srcw === 0 || srch === 0) return; - if (layer.autosize != 0) fitRect(x, y, srcw, srch); - displayContext.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch); + if (layer.autosize) fitRect(x, y, srcw, srch); + context.drawImage(srcCanvas, srcx, srcy, srcw, srch, x, y, srcw, srch); - }); }; /** @@ -724,18 +423,16 @@ Guacamole.Layer = function(width, height) { * @param {Number} y The Y coordinate of the point to draw. */ this.moveTo = function(x, y) { - scheduleTask(function() { - - // Start a new path if current path is closed - if (pathClosed) { - displayContext.beginPath(); - pathClosed = false; - } - - if (layer.autosize != 0) fitRect(x, y, 0, 0); - displayContext.moveTo(x, y); - - }); + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.moveTo(x, y); + }; /** @@ -745,18 +442,16 @@ Guacamole.Layer = function(width, height) { * @param {Number} y The Y coordinate of the endpoint of the line to draw. */ this.lineTo = function(x, y) { - scheduleTask(function() { - - // Start a new path if current path is closed - if (pathClosed) { - displayContext.beginPath(); - pathClosed = false; - } - - if (layer.autosize != 0) fitRect(x, y, 0, 0); - displayContext.lineTo(x, y); - - }); + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.lineTo(x, y); + }; /** @@ -773,18 +468,16 @@ Guacamole.Layer = function(width, height) { * decreasing angle. */ this.arc = function(x, y, radius, startAngle, endAngle, negative) { - scheduleTask(function() { - - // Start a new path if current path is closed - if (pathClosed) { - displayContext.beginPath(); - pathClosed = false; - } - - if (layer.autosize != 0) fitRect(x, y, 0, 0); - displayContext.arc(x, y, radius, startAngle, endAngle, negative); - - }); + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.arc(x, y, radius, startAngle, endAngle, negative); + }; /** @@ -798,18 +491,16 @@ Guacamole.Layer = function(width, height) { * @param {Number} y The Y coordinate of the endpoint of the curve. */ this.curveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) { - scheduleTask(function() { - - // Start a new path if current path is closed - if (pathClosed) { - displayContext.beginPath(); - pathClosed = false; - } - - if (layer.autosize != 0) fitRect(x, y, 0, 0); - displayContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); - - }); + + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, 0, 0); + context.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + }; /** @@ -817,13 +508,8 @@ Guacamole.Layer = function(width, height) { * point (if any) with a straight line. */ this.close = function() { - scheduleTask(function() { - - // Close path - displayContext.closePath(); - pathClosed = true; - - }); + context.closePath(); + pathClosed = true; }; /** @@ -837,18 +523,16 @@ Guacamole.Layer = function(width, height) { * @param {Number} h The height of the rectangle to draw. */ this.rect = function(x, y, w, h) { - scheduleTask(function() { - // Start a new path if current path is closed - if (pathClosed) { - displayContext.beginPath(); - pathClosed = false; - } - - if (layer.autosize != 0) fitRect(x, y, w, h); - displayContext.rect(x, y, w, h); - - }); + // Start a new path if current path is closed + if (pathClosed) { + context.beginPath(); + pathClosed = false; + } + + if (layer.autosize) fitRect(x, y, w, h); + context.rect(x, y, w, h); + }; /** @@ -858,15 +542,13 @@ Guacamole.Layer = function(width, height) { * once a path drawing operation (path() or rect()) is used. */ this.clip = function() { - scheduleTask(function() { - // Set new clipping region - displayContext.clip(); + // Set new clipping region + context.clip(); - // Path now implicitly closed - pathClosed = true; + // Path now implicitly closed + pathClosed = true; - }); }; /** @@ -886,19 +568,17 @@ Guacamole.Layer = function(width, height) { * @param {Number} a The alpha component of the color to fill. */ this.strokeColor = function(cap, join, thickness, r, g, b, a) { - scheduleTask(function() { - // Stroke with color - displayContext.lineCap = cap; - displayContext.lineJoin = join; - displayContext.lineWidth = thickness; - displayContext.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; - displayContext.stroke(); + // Stroke with color + context.lineCap = cap; + context.lineJoin = join; + context.lineWidth = thickness; + context.strokeStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; + context.stroke(); - // Path now implicitly closed - pathClosed = true; + // Path now implicitly closed + pathClosed = true; - }); }; /** @@ -913,16 +593,14 @@ Guacamole.Layer = function(width, height) { * @param {Number} a The alpha component of the color to fill. */ this.fillColor = function(r, g, b, a) { - scheduleTask(function() { - // Fill with color - displayContext.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; - displayContext.fill(); + // Fill with color + context.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a/255.0 + ")"; + context.fill(); - // Path now implicitly closed - pathClosed = true; + // Path now implicitly closed + pathClosed = true; - }); }; /** @@ -941,22 +619,20 @@ Guacamole.Layer = function(width, height) { * within the stroke. */ this.strokeLayer = function(cap, join, thickness, srcLayer) { - scheduleTaskSynced(srcLayer, function() { - // Stroke with image data - displayContext.lineCap = cap; - displayContext.lineJoin = join; - displayContext.lineWidth = thickness; - displayContext.strokeStyle = displayContext.createPattern( - srcLayer.getCanvas(), - "repeat" - ); - displayContext.stroke(); + // Stroke with image data + context.lineCap = cap; + context.lineJoin = join; + context.lineWidth = thickness; + context.strokeStyle = context.createPattern( + srcLayer.getCanvas(), + "repeat" + ); + context.stroke(); - // Path now implicitly closed - pathClosed = true; + // Path now implicitly closed + pathClosed = true; - }); }; /** @@ -970,47 +646,41 @@ Guacamole.Layer = function(width, height) { * within the fill. */ this.fillLayer = function(srcLayer) { - scheduleTask(function() { - // Fill with image data - displayContext.fillStyle = displayContext.createPattern( - srcLayer.getCanvas(), - "repeat" - ); - displayContext.fill(); + // Fill with image data + context.fillStyle = context.createPattern( + srcLayer.getCanvas(), + "repeat" + ); + context.fill(); - // Path now implicitly closed - pathClosed = true; + // Path now implicitly closed + pathClosed = true; - }); }; /** * Push current layer state onto stack. */ this.push = function() { - scheduleTask(function() { - // Save current state onto stack - displayContext.save(); - stackSize++; + // Save current state onto stack + context.save(); + stackSize++; - }); }; /** * Pop layer state off stack. */ this.pop = function() { - scheduleTask(function() { - // Restore current state from stack - if (stackSize > 0) { - displayContext.restore(); - stackSize--; - } + // Restore current state from stack + if (stackSize > 0) { + context.restore(); + stackSize--; + } - }); }; /** @@ -1018,23 +688,21 @@ Guacamole.Layer = function(width, height) { * matrix. */ this.reset = function() { - scheduleTask(function() { - // Clear stack - while (stackSize > 0) { - displayContext.restore(); - stackSize--; - } + // Clear stack + while (stackSize > 0) { + context.restore(); + stackSize--; + } - // Restore to initial state - displayContext.restore(); - displayContext.save(); + // Restore to initial state + context.restore(); + context.save(); - // Clear path - displayContext.beginPath(); - pathClosed = false; + // Clear path + context.beginPath(); + pathClosed = false; - }); }; /** @@ -1049,16 +717,11 @@ Guacamole.Layer = function(width, height) { * @param {Number} f The sixth value in the affine transform's matrix. */ this.setTransform = function(a, b, c, d, e, f) { - scheduleTask(function() { - - // Set transform - displayContext.setTransform( - a, b, c, - d, e, f - /*0, 0, 1*/ - ); - - }); + context.setTransform( + a, b, c, + d, e, f + /*0, 0, 1*/ + ); }; /** @@ -1073,16 +736,11 @@ Guacamole.Layer = function(width, height) { * @param {Number} f The sixth value in the affine transform's matrix. */ this.transform = function(a, b, c, d, e, f) { - scheduleTask(function() { - - // Apply transform - displayContext.transform( - a, b, c, - d, e, f - /*0, 0, 1*/ - ); - - }); + context.transform( + a, b, c, + d, e, f + /*0, 0, 1*/ + ); }; /** @@ -1098,9 +756,7 @@ Guacamole.Layer = function(width, height) { * Layer. */ this.setChannelMask = function(mask) { - scheduleTask(function() { - displayContext.globalCompositeOperation = compositeOperation[mask]; - }); + context.globalCompositeOperation = compositeOperation[mask]; }; /** @@ -1113,19 +769,17 @@ Guacamole.Layer = function(width, height) { * miter join. */ this.setMiterLimit = function(limit) { - scheduleTask(function() { - displayContext.miterLimit = limit; - }); + context.miterLimit = limit; }; // Initialize canvas dimensions - display.width = width; - display.height = height; + canvas.width = width; + canvas.height = height; // Explicitly render canvas below other elements in the layer (such as // child layers). Chrome and others may fail to render layers properly // without this. - display.style.zIndex = -1; + canvas.style.zIndex = -1; }; @@ -1237,3 +891,7 @@ Guacamole.Layer.Pixel = function(r, g, b, a) { this.alpha = a; }; + + +// FIXME: Declaration order hack +Guacamole.Display.VisibleLayer.prototype = new Guacamole.Layer(0, 0); diff --git a/guacamole/src/main/webapp/scripts/client-ui.js b/guacamole/src/main/webapp/scripts/client-ui.js index d1ee45215..9cd5ee66f 100644 --- a/guacamole/src/main/webapp/scripts/client-ui.js +++ b/guacamole/src/main/webapp/scripts/client-ui.js @@ -742,7 +742,7 @@ GuacUI.Client.Pinch = function(element) { GuacUI.Client.updateThumbnail = function() { // Get screenshot - var canvas = GuacUI.Client.attachedClient.flatten(); + var canvas = GuacUI.Client.attachedClient.getDisplay().flatten(); // Calculate scale of thumbnail (max 320x240, max zoom 100%) var scale = Math.min( @@ -781,7 +781,7 @@ GuacUI.Client.setScale = function(new_scale) { new_scale = Math.min(new_scale, GuacUI.Client.max_zoom); if (GuacUI.Client.attachedClient) - GuacUI.Client.attachedClient.scale(new_scale); + GuacUI.Client.attachedClient.getDisplay().scale(new_scale); GuacUI.Client.zoom_state.textContent = Math.round(new_scale * 100) + "%"; @@ -809,22 +809,22 @@ GuacUI.Client.updateDisplayScale = function() { // Determine whether display is currently fit to the screen var guac = GuacUI.Client.attachedClient; - var auto_fit = (guac.getScale() === GuacUI.Client.min_zoom); + var auto_fit = (guac.getDisplay().getScale() === GuacUI.Client.min_zoom); // Calculate scale to fit screen GuacUI.Client.min_zoom = Math.min( - window.innerWidth / Math.max(guac.getWidth(), 1), - window.innerHeight / Math.max(guac.getHeight(), 1) + window.innerWidth / Math.max(guac.getDisplay().getWidth(), 1), + window.innerHeight / Math.max(guac.getDisplay().getHeight(), 1) ); // Calculate appropriate maximum zoom level GuacUI.Client.max_zoom = Math.max(GuacUI.Client.min_zoom, 3); // Clamp zoom level, maintain auto-fit - if (guac.getScale() < GuacUI.Client.min_zoom || auto_fit) + if (guac.getDisplay().getScale() < GuacUI.Client.min_zoom || auto_fit) GuacUI.Client.setScale(GuacUI.Client.min_zoom); - else if (guac.getScale() > GuacUI.Client.max_zoom) + else if (guac.getDisplay().getScale() > GuacUI.Client.max_zoom) GuacUI.Client.setScale(GuacUI.Client.max_zoom); }; @@ -1055,7 +1055,7 @@ GuacUI.Client.setMouseEmulationAbsolute = function(absolute) { if (!guac) return; // Determine mouse position within view - var guac_display = guac.getDisplay(); + var guac_display = guac.getDisplay().getElement(); var mouse_view_x = mouseState.x + guac_display.offsetLeft - GuacUI.Client.main.scrollLeft; var mouse_view_y = mouseState.y + guac_display.offsetTop - GuacUI.Client.main.scrollTop; @@ -1087,8 +1087,8 @@ GuacUI.Client.setMouseEmulationAbsolute = function(absolute) { // Scale event by current scale var scaledState = new Guacamole.Mouse.State( - mouseState.x / guac.getScale(), - mouseState.y / guac.getScale(), + mouseState.x / guac.getDisplay().getScale(), + mouseState.y / guac.getDisplay().getScale(), mouseState.left, mouseState.middle, mouseState.right, @@ -1147,13 +1147,13 @@ GuacUI.Client.attach = function(guac) { GuacUI.Client.attachedClient = guac; // Get display element - var guac_display = guac.getDisplay(); + var guac_display = guac.getDisplay().getElement(); /* * Update the scale of the display when the client display size changes. */ - guac.onresize = function(width, height) { + guac.getDisplay().onresize = function(width, height) { GuacUI.Client.updateDisplayScale(); }; @@ -1332,8 +1332,8 @@ GuacUI.Client.attach = function(guac) { // Scale event by current scale var scaledState = new Guacamole.Mouse.State( - mouseState.x / guac.getScale(), - mouseState.y / guac.getScale(), + mouseState.x / guac.getDisplay().getScale(), + mouseState.y / guac.getDisplay().getScale(), mouseState.left, mouseState.middle, mouseState.right, @@ -1353,8 +1353,8 @@ GuacUI.Client.attach = function(guac) { GuacUI.Client.display.innerHTML = ""; // Add client to UI - guac.getDisplay().className = "software-cursor"; - GuacUI.Client.display.appendChild(guac.getDisplay()); + guac.getDisplay().getElement().className = "software-cursor"; + GuacUI.Client.display.appendChild(guac.getDisplay().getElement()); }; @@ -1698,7 +1698,7 @@ GuacUI.Client.attach = function(guac) { if (!guac) return; - initial_scale = guac.getScale(); + initial_scale = guac.getDisplay().getScale(); initial_center_x = (x + GuacUI.Client.main.scrollLeft) / initial_scale; initial_center_y = (y + GuacUI.Client.main.scrollTop) / initial_scale; }; @@ -2031,7 +2031,7 @@ GuacUI.Client.attach = function(guac) { // Zoom in by 10% var guac = GuacUI.Client.attachedClient; if (guac) - GuacUI.Client.setScale(guac.getScale() + 0.1); + GuacUI.Client.setScale(guac.getDisplay().getScale() + 0.1); }; @@ -2040,7 +2040,7 @@ GuacUI.Client.attach = function(guac) { // Zoom out by 10% var guac = GuacUI.Client.attachedClient; if (guac) - GuacUI.Client.setScale(guac.getScale() - 0.1); + GuacUI.Client.setScale(guac.getDisplay().getScale() - 0.1); };