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 * Dynamic on-screen keyboard. Given the URL to an XML keyboard layout file,
25 * this object will download and use the XML to construct a clickable on-screen
26 * keyboard with its own key events.
29 * @param {String} url The URL of an XML keyboard layout file.
31 Guacamole.OnScreenKeyboard = function(url) {
33 var allKeys = new Array();
34 var modifierState = new function() {};
36 function getKeySize(size) {
37 return (5*size) + "ex";
40 function getCapSize(size) {
41 return (5*size - 0.5) + "ex";
44 function clearModifiers() {
46 // Send key release events for all pressed modifiers
47 for (var k=0; k<allKeys.length; k++) {
50 var cap = key.getCap();
51 var modifier = cap.getModifier();
53 if (modifier && isModifierActive(modifier) && !cap.isSticky() && key.isPressed())
60 function setModifierReleased(modifier) {
61 if (isModifierActive(modifier))
62 modifierState[modifier]--;
65 function setModifierPressed(modifier) {
66 if (modifierState[modifier] == null)
67 modifierState[modifier] = 1;
69 modifierState[modifier]++;
72 function isModifierActive(modifier) {
73 if (modifierState[modifier] > 0)
79 function toggleModifierPressed(modifier) {
80 if (isModifierActive(modifier))
81 setModifierReleased(modifier);
83 setModifierPressed(modifier);
86 function refreshAllKeysState() {
87 for (var k=0; k<allKeys.length; k++)
88 allKeys[k].refreshState();
96 var displayText = cap.textContent;
100 if (cap.attributes["keysym"])
101 keysym = parseInt(cap.attributes["keysym"].value);
103 // If keysym not specified, get keysym from display text.
104 else if (displayText.length == 1) {
106 var charCode = displayText.charCodeAt(0);
108 if (charCode >= 0x0000 && charCode <= 0x00FF)
111 else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
112 keysym = 0x01000000 | charCode;
115 // Required modifiers for this keycap
117 if (cap.attributes["if"])
118 reqMod = cap.attributes["if"].value.split(",");
121 // Modifier represented by this keycap
123 if (cap.attributes["modifier"])
124 modifier = cap.attributes["modifier"].value;
127 // Whether this key is sticky (toggles)
128 // Currently only valid for modifiers.
130 if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
133 this.getDisplayText = function() {
134 return cap.textContent;
137 this.getKeySym = function() {
141 this.getRequiredModifiers = function() {
145 this.getModifier = function() {
149 this.isSticky = function() {
156 if (key.attributes["size"])
157 size = parseFloat(key.attributes["size"].value);
159 var caps = key.getElementsByTagName("cap");
160 var keycaps = new Array();
161 for (var i=0; i<caps.length; i++)
162 keycaps.push(new Cap(caps[i]));
164 var rowKey = document.createElement("div");
165 rowKey.className = "key";
167 var keyCap = document.createElement("div");
168 keyCap.className = "cap";
169 rowKey.appendChild(keyCap);
172 var STATE_RELEASED = 0;
173 var STATE_PRESSED = 1;
174 var state = STATE_RELEASED;
176 rowKey.isPressed = function() {
177 return state == STATE_PRESSED;
180 var currentCap = null;
181 function refreshState(modifier) {
185 for (var j=0; j<keycaps.length; j++) {
187 var keycap = keycaps[j];
188 var required = keycap.getRequiredModifiers();
192 // If modifiers required, make sure all modifiers are active
195 for (var k=0; k<required.length; k++) {
196 if (!isModifierActive(required[k])) {
209 rowKey.className = "key";
211 if (currentCap.getModifier())
212 rowKey.className += " modifier";
214 if (currentCap.isSticky())
215 rowKey.className += " sticky";
217 if (isModifierActive(currentCap.getModifier()))
218 rowKey.className += " active";
220 if (state == STATE_PRESSED)
221 rowKey.className += " pressed";
223 keyCap.textContent = currentCap.getDisplayText();
225 rowKey.refreshState = refreshState;
227 rowKey.getCap = function() {
235 rowKey.style.width = getKeySize(size);
236 keyCap.style.width = getCapSize(size);
241 // Set pressed, if released
244 if (state == STATE_RELEASED) {
246 state = STATE_PRESSED;
248 var keysym = currentCap.getKeySym();
249 var modifier = currentCap.getModifier();
250 var sticky = currentCap.isSticky();
252 if (keyPressedHandler && keysym)
253 keyPressedHandler(keysym);
257 // If sticky modifier, toggle
259 toggleModifierPressed(modifier);
261 // Otherwise, just set on.
263 setModifierPressed(modifier);
265 refreshAllKeysState();
272 rowKey.press = press;
275 // Set released, if pressed
278 if (state == STATE_PRESSED) {
280 state = STATE_RELEASED;
282 var keysym = currentCap.getKeySym();
283 var modifier = currentCap.getModifier();
284 var sticky = currentCap.isSticky();
286 if (keyReleasedHandler && keysym)
287 keyReleasedHandler(keysym);
291 // If not sticky modifier, release modifier
293 setModifierReleased(modifier);
294 refreshAllKeysState();
303 // If not a modifier, also release all pressed modifiers
310 rowKey.release = release;
312 // Toggle press/release states
314 if (state == STATE_PRESSED)
321 // Send key press on mousedown
322 rowKey.onmousedown = function(e) {
326 var modifier = currentCap.getModifier();
327 var sticky = currentCap.isSticky();
329 // Toggle non-sticky modifiers
330 if (modifier && !sticky)
340 // Send key release on mouseup/out
343 rowKey.onmouseup = function(e) {
347 var modifier = currentCap.getModifier();
348 var sticky = currentCap.isSticky();
350 // Release non-modifiers and sticky modifiers
351 if (!modifier || sticky)
357 rowKey.onselectstart = function() { return false; };
365 var keyboardGap = document.createElement("div");
366 keyboardGap.className = "gap";
367 keyboardGap.textContent = " ";
370 if (gap.attributes["size"])
371 size = parseFloat(gap.attributes["size"].value);
374 keyboardGap.style.width = getKeySize(size);
375 keyboardGap.style.height = getKeySize(size);
384 var keyboardRow = document.createElement("div");
385 keyboardRow.className = "row";
387 var children = row.childNodes;
388 for (var j=0; j<children.length; j++) {
389 var child = children[j];
391 // <row> can contain <key> or <column>
392 if (child.tagName == "key") {
393 var key = new Key(child);
394 keyboardRow.appendChild(key);
397 else if (child.tagName == "gap") {
398 var gap = new Gap(child);
399 keyboardRow.appendChild(gap);
401 else if (child.tagName == "column") {
402 var col = new Column(child);
403 keyboardRow.appendChild(col);
412 function Column(col) {
414 var keyboardCol = document.createElement("div");
415 keyboardCol.className = "col";
418 if (col.attributes["align"])
419 align = col.attributes["align"].value;
421 var children = col.childNodes;
422 for (var j=0; j<children.length; j++) {
423 var child = children[j];
425 // <column> can only contain <row>
426 if (child.tagName == "row") {
427 var row = new Row(child);
428 keyboardCol.appendChild(row);
434 keyboardCol.style.textAlign = align;
443 var keyboard = document.createElement("div");
444 keyboard.className = "keyboard";
447 // Retrieve keyboard XML
448 var xmlhttprequest = new XMLHttpRequest();
449 xmlhttprequest.open("GET", url, false);
450 xmlhttprequest.send(null);
452 var xml = xmlhttprequest.responseXML;
457 var root = xml.documentElement;
460 var children = root.childNodes;
461 for (var i=0; i<children.length; i++) {
462 var child = children[i];
464 // <keyboard> can contain <row> or <column>
465 if (child.tagName == "row") {
466 keyboard.appendChild(new Row(child));
468 else if (child.tagName == "column") {
469 keyboard.appendChild(new Column(child));
478 var keyPressedHandler = null;
479 var keyReleasedHandler = null;
481 keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
482 keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
484 // Do not allow selection or mouse movement to propagate/register.
485 keyboard.onselectstart =
486 keyboard.onmousemove =
488 keyboard.onmousedown =