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 {