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 || {};
23 Guacamole.OnScreenKeyboard = function(url) {
25 var allKeys = new Array();
26 var modifierState = new function() {};
28 function getKeySize(size) {
29 return (5*size) + "ex";
32 function getCapSize(size) {
33 return (5*size - 0.5) + "ex";
36 function clearModifiers() {
38 // Send key release events for all pressed modifiers
39 for (var k=0; k<allKeys.length; k++) {
42 var cap = key.getCap();
43 var modifier = cap.getModifier();
45 if (modifier && isModifierActive(modifier) && !cap.isSticky() && key.isPressed())
52 function setModifierReleased(modifier) {
53 if (isModifierActive(modifier))
54 modifierState[modifier]--;
57 function setModifierPressed(modifier) {
58 if (modifierState[modifier] == null)
59 modifierState[modifier] = 1;
61 modifierState[modifier]++;
64 function isModifierActive(modifier) {
65 if (modifierState[modifier] > 0)
71 function toggleModifierPressed(modifier) {
72 if (isModifierActive(modifier))
73 setModifierReleased(modifier);
75 setModifierPressed(modifier);
78 function refreshAllKeysState() {
79 for (var k=0; k<allKeys.length; k++)
80 allKeys[k].refreshState();
88 var displayText = cap.textContent;
92 if (cap.attributes["keysym"])
93 keysym = parseInt(cap.attributes["keysym"].value);
95 // If keysym not specified, get keysym from display text.
96 else if (displayText.length == 1) {
98 var charCode = displayText.charCodeAt(0);
100 if (charCode >= 0x0000 && charCode <= 0x00FF)
103 else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
104 keysym = 0x01000000 | charCode;
107 // Required modifiers for this keycap
109 if (cap.attributes["if"])
110 reqMod = cap.attributes["if"].value.split(",");
113 // Modifier represented by this keycap
115 if (cap.attributes["modifier"])
116 modifier = cap.attributes["modifier"].value;
119 // Whether this key is sticky (toggles)
120 // Currently only valid for modifiers.
122 if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
125 this.getDisplayText = function() {
126 return cap.textContent;
129 this.getKeySym = function() {
133 this.getRequiredModifiers = function() {
137 this.getModifier = function() {
141 this.isSticky = function() {
148 if (key.attributes["size"])
149 size = parseFloat(key.attributes["size"].value);
151 var caps = key.getElementsByTagName("cap");
152 var keycaps = new Array();
153 for (var i=0; i<caps.length; i++)
154 keycaps.push(new Cap(caps[i]));
156 var rowKey = document.createElement("div");
157 rowKey.className = "key";
159 var keyCap = document.createElement("div");
160 keyCap.className = "cap";
161 rowKey.appendChild(keyCap);
164 var STATE_RELEASED = 0;
165 var STATE_PRESSED = 1;
166 var state = STATE_RELEASED;
168 rowKey.isPressed = function() {
169 return state == STATE_PRESSED;
172 var currentCap = null;
173 function refreshState(modifier) {
177 for (var j=0; j<keycaps.length; j++) {
179 var keycap = keycaps[j];
180 var required = keycap.getRequiredModifiers();
184 // If modifiers required, make sure all modifiers are active
187 for (var k=0; k<required.length; k++) {
188 if (!isModifierActive(required[k])) {
201 rowKey.className = "key";
203 if (currentCap.getModifier())
204 rowKey.className += " modifier";
206 if (currentCap.isSticky())
207 rowKey.className += " sticky";
209 if (isModifierActive(currentCap.getModifier()))
210 rowKey.className += " active";
212 if (state == STATE_PRESSED)
213 rowKey.className += " pressed";
215 keyCap.textContent = currentCap.getDisplayText();
217 rowKey.refreshState = refreshState;
219 rowKey.getCap = function() {
227 rowKey.style.width = getKeySize(size);
228 keyCap.style.width = getCapSize(size);
233 // Set pressed, if released
236 if (state == STATE_RELEASED) {
238 state = STATE_PRESSED;
240 var keysym = currentCap.getKeySym();
241 var modifier = currentCap.getModifier();
242 var sticky = currentCap.isSticky();
244 if (keyPressedHandler && keysym)
245 keyPressedHandler(keysym);
249 // If sticky modifier, toggle
251 toggleModifierPressed(modifier);
253 // Otherwise, just set on.
255 setModifierPressed(modifier);
257 refreshAllKeysState();
264 rowKey.press = press;
267 // Set released, if pressed
270 if (state == STATE_PRESSED) {
272 state = STATE_RELEASED;
274 var keysym = currentCap.getKeySym();
275 var modifier = currentCap.getModifier();
276 var sticky = currentCap.isSticky();
278 if (keyReleasedHandler && keysym)
279 keyReleasedHandler(keysym);
283 // If not sticky modifier, release modifier
285 setModifierReleased(modifier);
286 refreshAllKeysState();
295 // If not a modifier, also release all pressed modifiers
302 rowKey.release = release;
304 // Toggle press/release states
306 if (state == STATE_PRESSED)
313 // Send key press on mousedown
314 rowKey.onmousedown = function(e) {
318 var modifier = currentCap.getModifier();
319 var sticky = currentCap.isSticky();
321 // Toggle non-sticky modifiers
322 if (modifier && !sticky)
332 // Send key release on mouseup/out
335 rowKey.onmouseup = function(e) {
339 var modifier = currentCap.getModifier();
340 var sticky = currentCap.isSticky();
342 // Release non-modifiers and sticky modifiers
343 if (!modifier || sticky)
349 rowKey.onselectstart = function() { return false; };
357 var keyboardGap = document.createElement("div");
358 keyboardGap.className = "gap";
359 keyboardGap.textContent = " ";
362 if (gap.attributes["size"])
363 size = parseFloat(gap.attributes["size"].value);
366 keyboardGap.style.width = getKeySize(size);
367 keyboardGap.style.height = getKeySize(size);
376 var keyboardRow = document.createElement("div");
377 keyboardRow.className = "row";
379 var children = row.childNodes;
380 for (var j=0; j<children.length; j++) {
381 var child = children[j];
383 // <row> can contain <key> or <column>
384 if (child.tagName == "key") {
385 var key = new Key(child);
386 keyboardRow.appendChild(key);
389 else if (child.tagName == "gap") {
390 var gap = new Gap(child);
391 keyboardRow.appendChild(gap);
393 else if (child.tagName == "column") {
394 var col = new Column(child);
395 keyboardRow.appendChild(col);
404 function Column(col) {
406 var keyboardCol = document.createElement("div");
407 keyboardCol.className = "col";
410 if (col.attributes["align"])
411 align = col.attributes["align"].value;
413 var children = col.childNodes;
414 for (var j=0; j<children.length; j++) {
415 var child = children[j];
417 // <column> can only contain <row>
418 if (child.tagName == "row") {
419 var row = new Row(child);
420 keyboardCol.appendChild(row);
426 keyboardCol.style.textAlign = align;
435 var keyboard = document.createElement("div");
436 keyboard.className = "keyboard";
439 // Retrieve keyboard XML
440 var xmlhttprequest = new XMLHttpRequest();
441 xmlhttprequest.open("GET", url, false);
442 xmlhttprequest.send(null);
444 var xml = xmlhttprequest.responseXML;
449 var root = xml.documentElement;
452 var children = root.childNodes;
453 for (var i=0; i<children.length; i++) {
454 var child = children[i];
456 // <keyboard> can contain <row> or <column>
457 if (child.tagName == "row") {
458 keyboard.appendChild(new Row(child));
460 else if (child.tagName == "column") {
461 keyboard.appendChild(new Column(child));
470 var keyPressedHandler = null;
471 var keyReleasedHandler = null;
473 keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
474 keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
476 // Do not allow selection or mouse movement to propagate/register.
477 keyboard.onselectstart =
478 keyboard.onmousemove =
480 keyboard.onmousedown =