diff --git a/guacamole/src/main/webapp/client.xhtml b/guacamole/src/main/webapp/client.xhtml index 586fa0cca..f5dc86190 100644 --- a/guacamole/src/main/webapp/client.xhtml +++ b/guacamole/src/main/webapp/client.xhtml @@ -40,6 +40,12 @@ + +
+ + +
+
diff --git a/guacamole/src/main/webapp/scripts/interface.js b/guacamole/src/main/webapp/scripts/interface.js index c38f0df9e..81e9a54b6 100644 --- a/guacamole/src/main/webapp/scripts/interface.js +++ b/guacamole/src/main/webapp/scripts/interface.js @@ -22,29 +22,36 @@ var GuacamoleUI = { /* Constants */ + "LONG_PRESS_DETECT_TIMEOUT" : 800, /* milliseconds */ + "LONG_PRESS_MOVEMENT_THRESHOLD" : 10, /* pixels */ "KEYBOARD_AUTO_RESIZE_INTERVAL" : 30, /* milliseconds */ /* UI Components */ - "viewport" : document.getElementById("viewportClone"), - "display" : document.getElementById("display"), - "logo" : document.getElementById("status-logo"), + "viewport" : document.getElementById("viewportClone"), + "display" : document.getElementById("display"), + "logo" : document.getElementById("status-logo"), + "eventTarget" : document.getElementById("eventTarget"), "buttons": { "reconnect" : document.getElementById("reconnect") }, "containers": { - "state" : document.getElementById("statusDialog"), - "keyboard" : document.getElementById("keyboardContainer") + "state" : document.getElementById("statusDialog"), + "keyboard" : document.getElementById("keyboardContainer"), + "magnifier" : document.getElementById("magnifier") }, + "magnifier" : document.getElementById("magnifier-display"), "state" : document.getElementById("statusText"), "client" : null, "sessionState" : new GuacamoleSessionState() }; +GuacamoleUI.magnifierContext = GuacamoleUI.magnifier.getContext("2d"); + /** * Array of all supported audio mimetypes, populated when this script is * loaded. @@ -107,6 +114,124 @@ GuacamoleUI.toggleKeyboard = function() { }; +GuacamoleUI.Magnifier = new (function() { + + var guac_magnifier = this; + + var position_x = 0; + var position_y = 0; + + var start_x = 0; + var start_y = 0; + + GuacamoleUI.containers.magnifier.addEventListener("touchstart", function(e) { + + if (e.touches.length == 1) { + + start_x = e.touches[0].screenX; + start_y = e.touches[0].screenY; + + } + + }, false); + + GuacamoleUI.containers.magnifier.addEventListener("touchmove", function(e) { + + if (e.touches.length == 1) { + + var width = GuacamoleUI.containers.magnifier.offsetWidth; + var height = GuacamoleUI.containers.magnifier.offsetHeight; + + var new_x = e.touches[0].screenX; + var new_y = e.touches[0].screenY; + + position_x += new_x - start_x; + position_y += new_y - start_y; + + if (position_x < 0) position_x = 0; + else if (position_x > window.innerWidth - width) + position_x = window.innerWidth - width; + + if (position_y < 0) position_y = 0; + else if (position_y > window.innerHeight - height) + position_y = window.innerHeight - height; + + start_x = new_x; + start_y = new_y; + + // Move magnifier to new position + guac_magnifier.move(position_x, position_y); + + // Update contents relative to new position + var clip_x = position_x + / (window.innerWidth - width) * (GuacamoleUI.client.getWidth() - width); + var clip_y = position_y + / (window.innerHeight - height) * (GuacamoleUI.client.getHeight() - height); + + GuacamoleUI.magnifier.style.WebkitTransform = + GuacamoleUI.magnifier.style.MozTransform = + GuacamoleUI.magnifier.style.OTransform = + GuacamoleUI.magnifier.style.msTransform = + GuacamoleUI.magnifier.style.transform = "translate(" + + (-clip_x) + "px, " + (-clip_y) + "px)"; + + } + + e.preventDefault(); + }, false); + + GuacamoleUI.containers.magnifier.addEventListener("click", function(e) { + GuacamoleUI.eventTarget.focus(); + }, false); + + this.move = function(x, y) { + + GuacamoleUI.containers.magnifier.style.WebkitTransform = + GuacamoleUI.containers.magnifier.style.MozTransform = + GuacamoleUI.containers.magnifier.style.OTransform = + GuacamoleUI.containers.magnifier.style.msTransform = + GuacamoleUI.containers.magnifier.style.transform = "translate(" + + x + "px, " + y + "px)"; + + }; + + this.update = function() { + + GuacamoleUI.magnifierContext.drawImage(GuacamoleUI.client.flatten(), + 0, 0); + + }; + + this.show = function(x, y) { + + // Get client dimensions + var width = GuacamoleUI.client.getWidth(); + var height = GuacamoleUI.client.getHeight(); + + // Resize to fit client + GuacamoleUI.magnifier.width = width; + GuacamoleUI.magnifier.height = height; + + // Ensure transformations on display originate at 0,0 + GuacamoleUI.containers.magnifier.style.transformOrigin = + GuacamoleUI.containers.magnifier.style.webkitTransformOrigin = + GuacamoleUI.containers.magnifier.style.MozTransformOrigin = + GuacamoleUI.containers.magnifier.style.OTransformOrigin = + GuacamoleUI.containers.magnifier.style.msTransformOrigin = + "0 0"; + + // Show magnifier + GuacamoleUI.containers.magnifier.style.display = "block"; + guac_magnifier.update(); + + }; + + this.hide = function() { + GuacamoleUI.containers.magnifier.style.display = "none"; + }; + +})(); + // If Node.classList is supported, implement addClass/removeClass using that if (Node.classList) { @@ -540,4 +665,57 @@ GuacamoleUI.attach = function(guac) { }; + var long_press_start_x = 0; + var long_press_start_y = 0; + var longPressTimeout = null; + + GuacamoleUI.startLongPressDetect = function() { + + if (!longPressTimeout) { + + longPressTimeout = window.setTimeout(function() { + longPressTimeout = null; + GuacamoleUI.Magnifier.show(); + }, GuacamoleUI.LONG_PRESS_DETECT_TIMEOUT); + + } + }; + + GuacamoleUI.stopLongPressDetect = function() { + window.clearTimeout(longPressTimeout); + longPressTimeout = null; + }; + + // Detect long-press at bottom of screen + GuacamoleUI.display.addEventListener('touchstart', function(e) { + + // Close magnifier + GuacamoleUI.Magnifier.hide(); + + // Record touch location + if (e.touches.length == 1) { + var touch = e.touches[0]; + long_press_start_x = touch.screenX; + long_press_start_y = touch.screenY; + } + + // Start detection + GuacamoleUI.startLongPressDetect(); + + }, true); + + // Stop detection if touch moves significantly + GuacamoleUI.display.addEventListener('touchmove', function(e) { + + // If touch distance from start exceeds threshold, cancel long press + var touch = e.touches[0]; + if (Math.abs(touch.screenX - long_press_start_x) >= GuacamoleUI.LONG_PRESS_MOVEMENT_THRESHOLD + || Math.abs(touch.screenY - long_press_start_y) >= GuacamoleUI.LONG_PRESS_MOVEMENT_THRESHOLD) + GuacamoleUI.stopLongPressDetect(); + + }, true); + + // Stop detection if press stops + GuacamoleUI.display.addEventListener('touchend', GuacamoleUI.stopLongPressDetect, true); + }; diff --git a/guacamole/src/main/webapp/styles/client.css b/guacamole/src/main/webapp/styles/client.css index 2723f1044..a46e92be4 100644 --- a/guacamole/src/main/webapp/styles/client.css +++ b/guacamole/src/main/webapp/styles/client.css @@ -41,6 +41,13 @@ img { -webkit-tap-highlight-color: rgba(0,0,0,0); } +#eventTarget { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; +} + /* Dialogs */ div.dialogOuter { @@ -163,6 +170,21 @@ div#display > * { margin-right: auto; } +div#magnifier { + + display: none; + position: absolute; + left: 0; + right: 0; + z-index: 1; + + box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.75); + width: 50%; + height: 50%; + overflow: hidden; + +} + /* Viewport Clone */ div#viewportClone {