2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is guacamole-common-js.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2010
20 * the Initial Developer. All Rights Reserved.
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 // Guacamole namespace
39 var Guacamole = Guacamole || {};
42 * Dynamic on-screen keyboard. Given the URL to an XML keyboard layout file,
43 * this object will download and use the XML to construct a clickable on-screen
44 * keyboard with its own key events.
47 * @param {String} url The URL of an XML keyboard layout file.
49 Guacamole.OnScreenKeyboard = function(url) {
51 var allKeys = new Array();
52 var modifierState = new function() {};
54 function getKeySize(size) {
55 return (5*size) + "ex";
58 function getCapSize(size) {
59 return (5*size - 0.5) + "ex";
62 function clearModifiers() {
64 // Send key release events for all pressed modifiers
65 for (var k=0; k<allKeys.length; k++) {
68 var cap = key.getCap();
69 var modifier = cap.getModifier();
71 if (modifier && isModifierActive(modifier) && !cap.isSticky() && key.isPressed())
78 function setModifierReleased(modifier) {
79 if (isModifierActive(modifier))
80 modifierState[modifier]--;
83 function setModifierPressed(modifier) {
84 if (modifierState[modifier] == null)
85 modifierState[modifier] = 1;
87 modifierState[modifier]++;
90 function isModifierActive(modifier) {
91 if (modifierState[modifier] > 0)
97 function toggleModifierPressed(modifier) {
98 if (isModifierActive(modifier))
99 setModifierReleased(modifier);
101 setModifierPressed(modifier);
104 function refreshAllKeysState() {
105 for (var k=0; k<allKeys.length; k++)
106 allKeys[k].refreshState();
114 var displayText = cap.textContent;
115 if (!displayText) displayText = cap.text;
119 if (cap.attributes["keysym"])
120 keysym = parseInt(cap.attributes["keysym"].value);
122 // If keysym not specified, get keysym from display text.
123 else if (displayText.length == 1) {
125 var charCode = displayText.charCodeAt(0);
127 if (charCode >= 0x0000 && charCode <= 0x00FF)
130 else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
131 keysym = 0x01000000 | charCode;
134 // Required modifiers for this keycap
136 if (cap.attributes["if"])
137 reqMod = cap.attributes["if"].value.split(",");
140 // Modifier represented by this keycap
142 if (cap.attributes["modifier"])
143 modifier = cap.attributes["modifier"].value;
146 // Whether this key is sticky (toggles)
147 // Currently only valid for modifiers.
149 if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
152 this.getDisplayText = function() {
156 this.getKeySym = function() {
160 this.getRequiredModifiers = function() {
164 this.getModifier = function() {
168 this.isSticky = function() {
175 if (key.attributes["size"])
176 size = parseFloat(key.attributes["size"].value);
178 var caps = key.getElementsByTagName("cap");
179 var keycaps = new Array();
180 for (var i=0; i<caps.length; i++)
181 keycaps.push(new Cap(caps[i]));
183 var rowKey = document.createElement("div");
184 rowKey.className = "key";
186 var keyCap = document.createElement("div");
187 keyCap.className = "cap";
188 rowKey.appendChild(keyCap);
191 var STATE_RELEASED = 0;
192 var STATE_PRESSED = 1;
193 var state = STATE_RELEASED;
195 rowKey.isPressed = function() {
196 return state == STATE_PRESSED;
199 var currentCap = null;
200 function refreshState(modifier) {
204 for (var j=0; j<keycaps.length; j++) {
206 var keycap = keycaps[j];
207 var required = keycap.getRequiredModifiers();
211 // If modifiers required, make sure all modifiers are active
214 for (var k=0; k<required.length; k++) {
215 if (!isModifierActive(required[k])) {
228 rowKey.className = "key";
230 if (currentCap.getModifier())
231 rowKey.className += " modifier";
233 if (currentCap.isSticky())
234 rowKey.className += " sticky";
236 if (isModifierActive(currentCap.getModifier()))
237 rowKey.className += " active";
239 if (state == STATE_PRESSED)
240 rowKey.className += " pressed";
242 keyCap.textContent = currentCap.getDisplayText();
244 rowKey.refreshState = refreshState;
246 rowKey.getCap = function() {
254 rowKey.style.width = getKeySize(size);
255 keyCap.style.width = getCapSize(size);
260 // Set pressed, if released
263 if (state == STATE_RELEASED) {
265 state = STATE_PRESSED;
267 var keysym = currentCap.getKeySym();
268 var modifier = currentCap.getModifier();
269 var sticky = currentCap.isSticky();
271 if (keyPressedHandler && keysym)
272 keyPressedHandler(keysym);
276 // If sticky modifier, toggle
278 toggleModifierPressed(modifier);
280 // Otherwise, just set on.
282 setModifierPressed(modifier);
284 refreshAllKeysState();
291 rowKey.press = press;
294 // Set released, if pressed
297 if (state == STATE_PRESSED) {
299 state = STATE_RELEASED;
301 var keysym = currentCap.getKeySym();
302 var modifier = currentCap.getModifier();
303 var sticky = currentCap.isSticky();
305 if (keyReleasedHandler && keysym)
306 keyReleasedHandler(keysym);
310 // If not sticky modifier, release modifier
312 setModifierReleased(modifier);
313 refreshAllKeysState();
322 // If not a modifier, also release all pressed modifiers
329 rowKey.release = release;
331 // Toggle press/release states
333 if (state == STATE_PRESSED)
340 // Send key press on mousedown
341 rowKey.onmousedown = function(e) {
345 var modifier = currentCap.getModifier();
346 var sticky = currentCap.isSticky();
348 // Toggle non-sticky modifiers
349 if (modifier && !sticky)
359 // Send key release on mouseup/out
362 rowKey.onmouseup = function(e) {
366 var modifier = currentCap.getModifier();
367 var sticky = currentCap.isSticky();
369 // Release non-modifiers and sticky modifiers
370 if (!modifier || sticky)
376 rowKey.onselectstart = function() { return false; };
384 var keyboardGap = document.createElement("div");
385 keyboardGap.className = "gap";
386 keyboardGap.textContent = " ";
389 if (gap.attributes["size"])
390 size = parseFloat(gap.attributes["size"].value);
393 keyboardGap.style.width = getKeySize(size);
394 keyboardGap.style.height = getKeySize(size);
403 var keyboardRow = document.createElement("div");
404 keyboardRow.className = "row";
406 var children = row.childNodes;
407 for (var j=0; j<children.length; j++) {
408 var child = children[j];
410 // <row> can contain <key> or <column>
411 if (child.tagName == "key") {
412 var key = new Key(child);
413 keyboardRow.appendChild(key);
416 else if (child.tagName == "gap") {
417 var gap = new Gap(child);
418 keyboardRow.appendChild(gap);
420 else if (child.tagName == "column") {
421 var col = new Column(child);
422 keyboardRow.appendChild(col);
431 function Column(col) {
433 var keyboardCol = document.createElement("div");
434 keyboardCol.className = "col";
437 if (col.attributes["align"])
438 align = col.attributes["align"].value;
440 var children = col.childNodes;
441 for (var j=0; j<children.length; j++) {
442 var child = children[j];
444 // <column> can only contain <row>
445 if (child.tagName == "row") {
446 var row = new Row(child);
447 keyboardCol.appendChild(row);
453 keyboardCol.style.textAlign = align;
462 var keyboard = document.createElement("div");
463 keyboard.className = "keyboard";
466 // Retrieve keyboard XML
467 var xmlhttprequest = new XMLHttpRequest();
468 xmlhttprequest.open("GET", url, false);
469 xmlhttprequest.send(null);
471 var xml = xmlhttprequest.responseXML;
476 var root = xml.documentElement;
479 var children = root.childNodes;
480 for (var i=0; i<children.length; i++) {
481 var child = children[i];
483 // <keyboard> can contain <row> or <column>
484 if (child.tagName == "row") {
485 keyboard.appendChild(new Row(child));
487 else if (child.tagName == "column") {
488 keyboard.appendChild(new Column(child));
497 var keyPressedHandler = null;
498 var keyReleasedHandler = null;
500 keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
501 keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
503 // Do not allow selection or mouse movement to propagate/register.
504 keyboard.onselectstart =
505 keyboard.onmousemove =
507 keyboard.onmousedown =