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/>.
21 function GuacamoleOnScreenKeyboard(url) {
24 var allKeys = new Array();
25 var modifierState = new function() {};
27 function getKeySize(size) {
28 return (5*size) + "ex";
31 function getCapSize(size) {
32 return (5*size - 0.5) + "ex";
35 function clearModifiers() {
37 // Send key release events for all pressed modifiers
38 for (var k=0; k<allKeys.length; k++) {
41 var cap = key.getCap();
42 var modifier = cap.getModifier();
44 if (modifier && isModifierActive(modifier) && !cap.isSticky() && key.isPressed())
51 function setModifierReleased(modifier) {
52 if (isModifierActive(modifier))
53 modifierState[modifier]--;
56 function setModifierPressed(modifier) {
57 if (modifierState[modifier] == null)
58 modifierState[modifier] = 1;
60 modifierState[modifier]++;
63 function isModifierActive(modifier) {
64 if (modifierState[modifier] > 0)
70 function toggleModifierPressed(modifier) {
71 if (isModifierActive(modifier))
72 setModifierReleased(modifier);
74 setModifierPressed(modifier);
77 function refreshAllKeysState() {
78 for (var k=0; k<allKeys.length; k++)
79 allKeys[k].refreshState();
87 var displayText = cap.textContent;
91 if (cap.attributes["keysym"])
92 keysym = parseInt(cap.attributes["keysym"].value);
94 // If keysym not specified, get keysym from display text.
95 else if (displayText.length == 1) {
97 var charCode = displayText.charCodeAt(0);
99 if (charCode >= 0x0000 && charCode <= 0x00FF)
102 else if (charCode >= 0x0100 && charCode <= 0x10FFFF)
103 keysym = 0x01000000 | charCode;
106 // Required modifiers for this keycap
108 if (cap.attributes["if"])
109 reqMod = cap.attributes["if"].value.split(",");
112 // Modifier represented by this keycap
114 if (cap.attributes["modifier"])
115 modifier = cap.attributes["modifier"].value;
118 // Whether this key is sticky (toggles)
119 // Currently only valid for modifiers.
121 if (cap.attributes["sticky"] && cap.attributes["sticky"].value == "true")
124 this.getDisplayText = function() {
125 return cap.textContent;
128 this.getKeySym = function() {
132 this.getRequiredModifiers = function() {
136 this.getModifier = function() {
140 this.isSticky = function() {
147 if (key.attributes["size"])
148 size = parseFloat(key.attributes["size"].value);
150 var caps = key.getElementsByTagName("cap");
151 var keycaps = new Array();
152 for (var i=0; i<caps.length; i++)
153 keycaps.push(new Cap(caps[i]));
155 var rowKey = document.createElement("div");
156 rowKey.className = "key";
158 var keyCap = document.createElement("div");
159 keyCap.className = "cap";
160 rowKey.appendChild(keyCap);
163 var STATE_RELEASED = 0;
164 var STATE_PRESSED = 1;
165 var state = STATE_RELEASED;
167 rowKey.isPressed = function() {
168 return state == STATE_PRESSED;
171 var currentCap = null;
172 function refreshState(modifier) {
176 for (var j=0; j<keycaps.length; j++) {
178 var keycap = keycaps[j];
179 var required = keycap.getRequiredModifiers();
183 // If modifiers required, make sure all modifiers are active
186 for (var k=0; k<required.length; k++) {
187 if (!isModifierActive(required[k])) {
200 rowKey.className = "key";
202 if (currentCap.getModifier())
203 rowKey.className += " modifier";
205 if (currentCap.isSticky())
206 rowKey.className += " sticky";
208 if (isModifierActive(currentCap.getModifier()))
209 rowKey.className += " active";
211 if (state == STATE_PRESSED)
212 rowKey.className += " pressed";
214 keyCap.textContent = currentCap.getDisplayText();
216 rowKey.refreshState = refreshState;
218 rowKey.getCap = function() {
226 rowKey.style.width = getKeySize(size);
227 keyCap.style.width = getCapSize(size);
232 // Set pressed, if released
235 if (state == STATE_RELEASED) {
237 state = STATE_PRESSED;
239 var keysym = currentCap.getKeySym();
240 var modifier = currentCap.getModifier();
241 var sticky = currentCap.isSticky();
243 if (keyPressedHandler && keysym)
244 keyPressedHandler(keysym);
248 // If sticky modifier, toggle
250 toggleModifierPressed(modifier);
252 // Otherwise, just set on.
254 setModifierPressed(modifier);
256 refreshAllKeysState();
263 rowKey.press = press;
266 // Set released, if pressed
269 if (state == STATE_PRESSED) {
271 state = STATE_RELEASED;
273 var keysym = currentCap.getKeySym();
274 var modifier = currentCap.getModifier();
275 var sticky = currentCap.isSticky();
277 if (keyReleasedHandler && keysym)
278 keyReleasedHandler(keysym);
282 // If not sticky modifier, release modifier
284 setModifierReleased(modifier);
285 refreshAllKeysState();
294 // If not a modifier, also release all pressed modifiers
301 rowKey.release = release;
303 // Toggle press/release states
305 if (state == STATE_PRESSED)
312 // Send key press on mousedown
313 rowKey.onmousedown = function(e) {
317 var modifier = currentCap.getModifier();
318 var sticky = currentCap.isSticky();
320 // Toggle non-sticky modifiers
321 if (modifier && !sticky)
331 // Send key release on mouseup/out
334 rowKey.onmouseup = function(e) {
338 var modifier = currentCap.getModifier();
339 var sticky = currentCap.isSticky();
341 // Release non-modifiers and sticky modifiers
342 if (!modifier || sticky)
348 rowKey.onselectstart = function() { return false; };
356 var keyboardGap = document.createElement("div");
357 keyboardGap.className = "gap";
358 keyboardGap.textContent = " ";
361 if (gap.attributes["size"])
362 size = parseFloat(gap.attributes["size"].value);
365 keyboardGap.style.width = getKeySize(size);
366 keyboardGap.style.height = getKeySize(size);
375 var keyboardRow = document.createElement("div");
376 keyboardRow.className = "row";
378 var children = row.childNodes;
379 for (var j=0; j<children.length; j++) {
380 var child = children[j];
382 // <row> can contain <key> or <column>
383 if (child.tagName == "key") {
384 var key = new Key(child);
385 keyboardRow.appendChild(key);
388 else if (child.tagName == "gap") {
389 var gap = new Gap(child);
390 keyboardRow.appendChild(gap);
392 else if (child.tagName == "column") {
393 var col = new Column(child);
394 keyboardRow.appendChild(col);
403 function Column(col) {
405 var keyboardCol = document.createElement("div");
406 keyboardCol.className = "col";
409 if (col.attributes["align"])
410 align = col.attributes["align"].value;
412 var children = col.childNodes;
413 for (var j=0; j<children.length; j++) {
414 var child = children[j];
416 // <column> can only contain <row>
417 if (child.tagName == "row") {
418 var row = new Row(child);
419 keyboardCol.appendChild(row);
425 keyboardCol.style.textAlign = align;
434 var keyboard = document.createElement("div");
435 keyboard.className = "keyboard";
438 // Retrieve keyboard XML
439 var xmlhttprequest = new XMLHttpRequest();
440 xmlhttprequest.open("GET", url, false);
441 xmlhttprequest.send(null);
443 var xml = xmlhttprequest.responseXML;
448 var root = xml.documentElement;
451 var children = root.childNodes;
452 for (var i=0; i<children.length; i++) {
453 var child = children[i];
455 // <keyboard> can contain <row> or <column>
456 if (child.tagName == "row") {
457 keyboard.appendChild(new Row(child));
459 else if (child.tagName == "column") {
460 keyboard.appendChild(new Column(child));
469 var keyPressedHandler = null;
470 var keyReleasedHandler = null;
472 keyboard.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
473 keyboard.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
475 // Do not allow selection or mouse movement to propagate/register.
476 keyboard.onselectstart =
477 keyboard.onmousemove =
479 keyboard.onmousedown =