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
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // Guacamole namespace
21 var Guacamole = Guacamole || {};
24 * Provides cross-browser mouse events for a given element. The events of
25 * the given element are automatically populated with handlers that translate
26 * mouse events into a non-browser-specific event provided by the
27 * Guacamole.Mouse instance.
29 * Touch event support is planned, but currently only in testing (translate
30 * touch events into mouse events).
33 * @param {Element} element The Element to use to provide mouse events.
35 Guacamole.Mouse = function(element) {
38 * Reference to this Guacamole.Mouse.
41 var guac_mouse = this;
44 * The current mouse state. The properties of this state are updated when
45 * mouse events fire. This state object is also passed in as a parameter to
46 * the handler of any mouse events.
48 * @type Guacamole.Mouse.State
50 this.currentState = new Guacamole.Mouse.State(
52 false, false, false, false, false
56 * Fired whenever the user presses a mouse button down over the element
57 * associated with this Guacamole.Mouse.
60 * @param {Guacamole.Mouse.State} state The current mouse state.
62 this.onmousedown = null;
65 * Fired whenever the user releases a mouse button down over the element
66 * associated with this Guacamole.Mouse.
69 * @param {Guacamole.Mouse.State} state The current mouse state.
71 this.onmouseup = null;
74 * Fired whenever the user moves the mouse over the element associated with
75 * this Guacamole.Mouse.
78 * @param {Guacamole.Mouse.State} state The current mouse state.
80 this.onmousemove = null;
82 function moveMouse(pageX, pageY) {
84 guac_mouse.currentState.x = pageX - element.offsetLeft;
85 guac_mouse.currentState.y = pageY - element.offsetTop;
87 // This is all JUST so we can get the mouse position within the element
88 var parent = element.offsetParent;
90 if (parent.offsetLeft && parent.offsetTop) {
91 guac_mouse.currentState.x -= parent.offsetLeft;
92 guac_mouse.currentState.y -= parent.offsetTop;
94 parent = parent.offsetParent;
97 if (guac_mouse.onmousemove)
98 guac_mouse.onmousemove(guac_mouse.currentState);
103 // Block context menu so right-click gets sent properly
104 element.oncontextmenu = function(e) {
108 element.onmousemove = function(e) {
110 // Don't handle if we aren't supposed to
111 if (gesture_in_progress) return;
115 moveMouse(e.pageX, e.pageY);
119 var last_touch_x = 0;
120 var last_touch_y = 0;
121 var last_touch_time = 0;
122 var pixels_moved = 0;
124 var gesture_in_progress = false;
125 var click_release_timeout = null;
127 element.ontouchend = function(e) {
129 // If we're handling a gesture
130 if (gesture_in_progress) {
135 var time = new Date().getTime();
137 // If mouse already down, release anad clear timeout
138 if (guac_mouse.currentState.left) {
140 // Fire left button up event
141 guac_mouse.currentState.left = false;
142 if (guac_mouse.onmouseup)
143 guac_mouse.onmouseup(guac_mouse.currentState);
145 // Clear timeout, if set
146 if (click_release_timeout) {
147 window.clearTimeout(click_release_timeout);
148 click_release_timeout = null;
153 // If single tap detected (based on time and distance)
154 if (time - last_touch_time <= 250 && pixels_moved < 10) {
156 // Fire left button down event
157 guac_mouse.currentState.left = true;
158 if (guac_mouse.onmousedown)
159 guac_mouse.onmousedown(guac_mouse.currentState);
161 // Delay mouse up - mouse up should be canceled if
162 // touchstart within timeout.
163 click_release_timeout = window.setTimeout(function() {
165 // Fire left button up event
166 guac_mouse.currentState.left = false;
167 if (guac_mouse.onmouseup)
168 guac_mouse.onmouseup(guac_mouse.currentState);
170 // Allow mouse events now that touching is over
171 gesture_in_progress = false;
181 element.ontouchstart = function(e) {
183 // Record initial touch location and time for single-touch movement
185 if (e.touches.length == 1) {
190 // Stop mouse events while touching
191 gesture_in_progress = true;
193 // Clear timeout, if set
194 if (click_release_timeout) {
195 window.clearTimeout(click_release_timeout);
196 click_release_timeout = null;
199 // Record touch location and time
200 var starting_touch = e.touches[0];
201 last_touch_x = starting_touch.pageX;
202 last_touch_y = starting_touch.pageY;
203 last_touch_time = new Date().getTime();
206 // TODO: Handle different buttons
212 element.ontouchmove = function(e) {
214 // Handle single-touch movement gesture (touchpad mouse move)
215 if (e.touches.length == 1) {
220 // Get change in touch location
221 var touch = e.touches[0];
222 var delta_x = touch.pageX - last_touch_x;
223 var delta_y = touch.pageY - last_touch_y;
225 // Track pixels moved
226 pixels_moved += Math.abs(delta_x) + Math.abs(delta_y);
228 // Update mouse location
229 guac_mouse.currentState.x += delta_x;
230 guac_mouse.currentState.y += delta_y;
232 // FIXME: Prevent mouse from leaving screen
234 // Fire movement event, if defined
235 if (guac_mouse.onmousemove)
236 guac_mouse.onmousemove(guac_mouse.currentState);
238 // Update touch location
239 last_touch_x = touch.pageX;
240 last_touch_y = touch.pageY;
247 element.onmousedown = function(e) {
249 // Don't handle if we aren't supposed to
250 if (gesture_in_progress) return;
256 guac_mouse.currentState.left = true;
259 guac_mouse.currentState.middle = true;
262 guac_mouse.currentState.right = true;
266 if (guac_mouse.onmousedown)
267 guac_mouse.onmousedown(guac_mouse.currentState);
272 element.onmouseup = function(e) {
274 // Don't handle if we aren't supposed to
275 if (gesture_in_progress) return;
281 guac_mouse.currentState.left = false;
284 guac_mouse.currentState.middle = false;
287 guac_mouse.currentState.right = false;
291 if (guac_mouse.onmouseup)
292 guac_mouse.onmouseup(guac_mouse.currentState);
296 element.onmouseout = function(e) {
298 // Don't handle if we aren't supposed to
299 if (gesture_in_progress) return;
303 // Release all buttons
304 if (guac_mouse.currentState.left
305 || guac_mouse.currentState.middle
306 || guac_mouse.currentState.right) {
308 guac_mouse.currentState.left = false;
309 guac_mouse.currentState.middle = false;
310 guac_mouse.currentState.right = false;
312 if (guac_mouse.onmouseup)
313 guac_mouse.onmouseup(guac_mouse.currentState);
318 // Override selection on mouse event element.
319 element.onselectstart = function() {
323 // Scroll wheel support
324 element.onmousewheel = function(e) {
326 // Don't handle if we aren't supposed to
327 if (gesture_in_progress) return;
332 else if (e.wheelDelta)
333 delta = -event.wheelDelta;
337 if (guac_mouse.onmousedown) {
338 guac_mouse.currentState.up = true;
339 guac_mouse.onmousedown(guac_mouse.currentState);
342 if (guac_mouse.onmouseup) {
343 guac_mouse.currentState.up = false;
344 guac_mouse.onmouseup(guac_mouse.currentState);
350 if (guac_mouse.onmousedown) {
351 guac_mouse.currentState.down = true;
352 guac_mouse.onmousedown(guac_mouse.currentState);
355 if (guac_mouse.onmouseup) {
356 guac_mouse.currentState.down = false;
357 guac_mouse.onmouseup(guac_mouse.currentState);
361 if (e.preventDefault)
364 e.returnValue = false;
368 element.addEventListener('DOMMouseScroll', element.onmousewheel, false);
373 * Simple container for properties describing the state of a mouse.
376 * @param {Number} x The X position of the mouse pointer in pixels.
377 * @param {Number} y The Y position of the mouse pointer in pixels.
378 * @param {Boolean} left Whether the left mouse button is pressed.
379 * @param {Boolean} middle Whether the middle mouse button is pressed.
380 * @param {Boolean} right Whether the right mouse button is pressed.
381 * @param {Boolean} up Whether the up mouse button is pressed (the fourth
382 * button, usually part of a scroll wheel).
383 * @param {Boolean} down Whether the down mouse button is pressed (the fifth
384 * button, usually part of a scroll wheel).
386 Guacamole.Mouse.State = function(x, y, left, middle, right, up, down) {
389 * The current X position of the mouse pointer.
395 * The current Y position of the mouse pointer.
401 * Whether the left mouse button is currently pressed.
407 * Whether the middle mouse button is currently pressed.
413 * Whether the right mouse button is currently pressed.
419 * Whether the up mouse button is currently pressed. This is the fourth
420 * mouse button, associated with upward scrolling of the mouse scroll
427 * Whether the down mouse button is currently pressed. This is the fifth
428 * mouse button, associated with downward scrolling of the mouse scroll