]> git.proxmox.com Git - mirror_novnc.git/blob - include/canvas.js
Some JSLint'ing
[mirror_novnc.git] / include / canvas.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9 /*jslint browser: true, white: false, bitwise: false */
10 /*global window, Util, Base64, changeCursor, getKeysym */
11
12 function Canvas(conf) {
13 "use strict";
14
15 conf = conf || {}; // Configuration
16 var that = {}, // Public API interface
17
18 // Private Canvas namespace variables
19 c_forceCanvas = false,
20
21 c_width = 0,
22 c_height = 0,
23
24 c_prevStyle = "",
25
26 c_keyPress = null,
27 c_mouseButton = null,
28 c_mouseMove = null,
29
30 c_webkit_bug = false,
31 c_flush_timer = null;
32
33 // Configuration settings
34 function cdef(v, type, defval, desc) {
35 Util.conf_default(conf, that, v, type, defval, desc); }
36
37 // Capability settings, default can be overridden
38 cdef('prefer_js', 'raw', null, 'Prefer Javascript over canvas methods');
39 cdef('cursor_uri', 'raw', null, 'Can we render cursor using data URI');
40
41 cdef('target', 'dom', null, 'Canvas element for VNC viewport');
42 cdef('focusContainer', 'dom', document, 'DOM element that traps keyboard input');
43 cdef('true_color', 'bool', true, 'Request true color pixel data');
44 cdef('focused', 'bool', true, 'Capture and send key strokes');
45 cdef('colourMap', 'raw', [], 'Colour map array (not true color)');
46 cdef('scale', 'float', 1, 'VNC viewport scale factor');
47
48 cdef('render_mode', 'str', '', 'Canvas rendering mode (read-only)');
49
50 // Override some specific getters/setters
51 that.set_prefer_js = function(val) {
52 if (val && c_forceCanvas) {
53 Util.Warn("Preferring Javascript to Canvas ops is not supported");
54 return false;
55 }
56 conf.prefer_js = val;
57 return true;
58 };
59
60 that.get_colourMap = function(idx) {
61 if (typeof idx === 'undefined') {
62 return conf.colourMap;
63 } else {
64 return conf.colourMap[idx];
65 }
66 };
67
68 that.set_colourMap = function(val, idx) {
69 if (typeof idx === 'undefined') {
70 conf.colourMap = val;
71 } else {
72 conf.colourMap[idx] = val;
73 }
74 };
75
76 that.set_render_mode = function () { throw("render_mode is read-only"); };
77
78 // Add some other getters/setters
79 that.get_width = function() {
80 return c_width;
81 };
82 that.get_height = function() {
83 return c_height;
84 };
85
86 //
87 // Private functions
88 //
89
90 // Create the public API interface
91 function constructor() {
92 Util.Debug(">> Canvas.init");
93
94 var c, ctx, func, origfunc, imgTest, tval, i, curDat, curSave,
95 has_imageData = false, UE = Util.Engine;
96
97 if (! conf.target) { throw("target must be set"); }
98
99 if (typeof conf.target === 'string') {
100 throw("target must be a DOM element");
101 }
102
103 c = conf.target;
104
105 if (! c.getContext) { throw("no getContext method"); }
106
107 if (! conf.ctx) { conf.ctx = c.getContext('2d'); }
108 ctx = conf.ctx;
109
110 Util.Debug("User Agent: " + navigator.userAgent);
111 if (UE.gecko) { Util.Debug("Browser: gecko " + UE.gecko); }
112 if (UE.webkit) { Util.Debug("Browser: webkit " + UE.webkit); }
113 if (UE.trident) { Util.Debug("Browser: trident " + UE.trident); }
114 if (UE.presto) { Util.Debug("Browser: presto " + UE.presto); }
115
116 that.clear();
117
118 /*
119 * Determine browser Canvas feature support
120 * and select fastest rendering methods
121 */
122 tval = 0;
123 try {
124 imgTest = ctx.getImageData(0, 0, 1,1);
125 imgTest.data[0] = 123;
126 imgTest.data[3] = 255;
127 ctx.putImageData(imgTest, 0, 0);
128 tval = ctx.getImageData(0, 0, 1, 1).data[0];
129 if (tval === 123) {
130 has_imageData = true;
131 }
132 } catch (exc1) {}
133
134 if (has_imageData) {
135 Util.Info("Canvas supports imageData");
136 c_forceCanvas = false;
137 if (ctx.createImageData) {
138 // If it's there, it's faster
139 Util.Info("Using Canvas createImageData");
140 conf.render_mode = "createImageData rendering";
141 that.imageData = that.imageDataCreate;
142 } else if (ctx.getImageData) {
143 // I think this is mostly just Opera
144 Util.Info("Using Canvas getImageData");
145 conf.render_mode = "getImageData rendering";
146 that.imageData = that.imageDataGet;
147 }
148 Util.Info("Prefering javascript operations");
149 if (conf.prefer_js === null) {
150 conf.prefer_js = true;
151 }
152 that.rgbxImage = that.rgbxImageData;
153 that.cmapImage = that.cmapImageData;
154 } else {
155 Util.Warn("Canvas lacks imageData, using fillRect (slow)");
156 conf.render_mode = "fillRect rendering (slow)";
157 c_forceCanvas = true;
158 conf.prefer_js = false;
159 that.rgbxImage = that.rgbxImageFill;
160 that.cmapImage = that.cmapImageFill;
161 }
162
163 if (UE.webkit && UE.webkit >= 534.7 && UE.webkit <= 534.9) {
164 // Workaround WebKit canvas rendering bug #46319
165 conf.render_mode += ", webkit bug workaround";
166 Util.Debug("Working around WebKit bug #46319");
167 c_webkit_bug = true;
168 for (func in {"fillRect":1, "copyImage":1, "rgbxImage":1,
169 "cmapImage":1, "blitStringImage":1}) {
170 that[func] = (function() {
171 var myfunc = that[func]; // Save original function
172 //Util.Debug("Wrapping " + func);
173 return function() {
174 myfunc.apply(this, arguments);
175 if (!c_flush_timer) {
176 c_flush_timer = setTimeout(that.flush, 100);
177 }
178 };
179 }());
180 }
181 }
182
183 /*
184 * Determine browser support for setting the cursor via data URI
185 * scheme
186 */
187 curDat = [];
188 for (i=0; i < 8 * 8 * 4; i += 1) {
189 curDat.push(255);
190 }
191 try {
192 curSave = c.style.cursor;
193 changeCursor(conf.target, curDat, curDat, 2, 2, 8, 8);
194 if (c.style.cursor) {
195 if (conf.cursor_uri === null) {
196 conf.cursor_uri = true;
197 }
198 Util.Info("Data URI scheme cursor supported");
199 } else {
200 if (conf.cursor_uri === null) {
201 conf.cursor_uri = false;
202 }
203 Util.Warn("Data URI scheme cursor not supported");
204 }
205 c.style.cursor = curSave;
206 } catch (exc2) {
207 Util.Error("Data URI scheme cursor test exception: " + exc2);
208 conf.cursor_uri = false;
209 }
210
211 conf.focused = true;
212
213 Util.Debug("<< Canvas.init");
214 return that ;
215 }
216
217 function onMouseButton(e, down) {
218 var evt, pos, bmask;
219 if (! conf.focused) {
220 return true;
221 }
222 evt = (e ? e : window.event);
223 pos = Util.getEventPosition(e, conf.target, conf.scale);
224 bmask = 1 << evt.button;
225 //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
226 if (c_mouseButton) {
227 c_mouseButton(pos.x, pos.y, down, bmask);
228 }
229 Util.stopEvent(e);
230 return false;
231 }
232
233 function onMouseDown(e) {
234 onMouseButton(e, 1);
235 }
236
237 function onMouseUp(e) {
238 onMouseButton(e, 0);
239 }
240
241 function onMouseWheel(e) {
242 var evt, pos, bmask, wheelData;
243 evt = (e ? e : window.event);
244 pos = Util.getEventPosition(e, conf.target, conf.scale);
245 wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
246 if (wheelData > 0) {
247 bmask = 1 << 3;
248 } else {
249 bmask = 1 << 4;
250 }
251 //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
252 if (c_mouseButton) {
253 c_mouseButton(pos.x, pos.y, 1, bmask);
254 c_mouseButton(pos.x, pos.y, 0, bmask);
255 }
256 Util.stopEvent(e);
257 return false;
258 }
259
260 function onMouseMove(e) {
261 var evt, pos;
262 evt = (e ? e : window.event);
263 pos = Util.getEventPosition(e, conf.target, conf.scale);
264 //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
265 if (c_mouseMove) {
266 c_mouseMove(pos.x, pos.y);
267 }
268 }
269
270 function onKeyDown(e) {
271 //Util.Debug("keydown: " + getKeysym(e));
272 if (! conf.focused) {
273 return true;
274 }
275 if (c_keyPress) {
276 c_keyPress(getKeysym(e), 1, e.ctrlKey, e.shiftKey, e.altKey);
277 }
278 Util.stopEvent(e);
279 return false;
280 }
281
282 function onKeyUp(e) {
283 //Util.Debug("keyup: " + getKeysym(e));
284 if (! conf.focused) {
285 return true;
286 }
287 if (c_keyPress) {
288 c_keyPress(getKeysym(e), 0, e.ctrlKey, e.shiftKey, e.altKey);
289 }
290 Util.stopEvent(e);
291 return false;
292 }
293
294 function onKeyPress(e) {
295 //Util.Debug("keypress: " + e.charCode);
296 if (! conf.focused) {
297 return true;
298 }
299 // Stop keypress events. Necessary for Opera because stopping
300 // keydown and keyup events still results in a keypress event.
301 Util.stopEvent(e);
302 return false;
303 }
304
305 function onMouseDisable(e) {
306 var evt, pos;
307 if (! conf.focused) {
308 return true;
309 }
310 evt = (e ? e : window.event);
311 pos = Util.getEventPosition(e, conf.target, conf.scale);
312 /* Stop propagation if inside canvas area */
313 if ((pos.x >= 0) && (pos.y >= 0) &&
314 (pos.x < c_width) && (pos.y < c_height)) {
315 //Util.Debug("mouse event disabled");
316 Util.stopEvent(e);
317 return false;
318 }
319 //Util.Debug("mouse event not disabled");
320 return true;
321 }
322
323 //
324 // Public API interface functions
325 //
326
327 that.getContext = function () {
328 return conf.ctx;
329 };
330
331 that.start = function(keyPressFunc, mouseButtonFunc, mouseMoveFunc) {
332 var c;
333 Util.Debug(">> Canvas.start");
334
335 c = conf.target;
336 c_keyPress = keyPressFunc || null;
337 c_mouseButton = mouseButtonFunc || null;
338 c_mouseMove = mouseMoveFunc || null;
339
340 Util.addEvent(conf.focusContainer, 'keydown', onKeyDown);
341 Util.addEvent(conf.focusContainer, 'keyup', onKeyUp);
342 Util.addEvent(conf.focusContainer, 'keypress', onKeyPress);
343 Util.addEvent(c, 'mousedown', onMouseDown);
344 Util.addEvent(c, 'mouseup', onMouseUp);
345 Util.addEvent(c, 'mousemove', onMouseMove);
346 Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
347 onMouseWheel);
348
349 /* Work around right and middle click browser behaviors */
350 Util.addEvent(conf.focusContainer, 'click', onMouseDisable);
351 Util.addEvent(conf.focusContainer.body, 'contextmenu', onMouseDisable);
352
353 Util.Debug("<< Canvas.start");
354 };
355
356 that.stop = function() {
357 var c = conf.target;
358 Util.removeEvent(conf.focusContainer, 'keydown', onKeyDown);
359 Util.removeEvent(conf.focusContainer, 'keyup', onKeyUp);
360 Util.removeEvent(conf.focusContainer, 'keypress', onKeyPress);
361 Util.removeEvent(c, 'mousedown', onMouseDown);
362 Util.removeEvent(c, 'mouseup', onMouseUp);
363 Util.removeEvent(c, 'mousemove', onMouseMove);
364 Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
365 onMouseWheel);
366
367 /* Work around right and middle click browser behaviors */
368 Util.removeEvent(conf.focusContainer, 'click', onMouseDisable);
369 Util.removeEvent(conf.focusContainer.body, 'contextmenu', onMouseDisable);
370
371 // Turn off cursor rendering
372 if (conf.cursor_uri) {
373 c.style.cursor = "default";
374 }
375 };
376
377 that.rescale = function(factor) {
378 var c, tp, x, y,
379 properties = ['transform', 'WebkitTransform', 'MozTransform', null];
380 c = conf.target;
381 tp = properties.shift();
382 while (tp) {
383 if (typeof c.style[tp] !== 'undefined') {
384 break;
385 }
386 tp = properties.shift();
387 }
388
389 if (tp === null) {
390 Util.Debug("No scaling support");
391 return;
392 }
393
394 if (conf.scale === factor) {
395 //Util.Debug("Canvas already scaled to '" + factor + "'");
396 return;
397 }
398
399 conf.scale = factor;
400 x = c.width - c.width * factor;
401 y = c.height - c.height * factor;
402 c.style[tp] = "scale(" + conf.scale + ") translate(-" + x + "px, -" + y + "px)";
403 };
404
405 that.resize = function(width, height, true_color) {
406 var c = conf.target;
407
408 if (typeof true_color !== "undefined") {
409 conf.true_color = true_color;
410 }
411 c_prevStyle = "";
412
413 c.width = width;
414 c.height = height;
415
416 c_width = c.offsetWidth;
417 c_height = c.offsetHeight;
418
419 that.rescale(conf.scale);
420 };
421
422 that.clear = function() {
423 that.resize(640, 20);
424 conf.ctx.clearRect(0, 0, c_width, c_height);
425
426 // No benefit over default ("source-over") in Chrome and firefox
427 //conf.ctx.globalCompositeOperation = "copy";
428 };
429
430 that.flush = function() {
431 var old_val;
432 //Util.Debug(">> flush");
433 // Force canvas redraw (for webkit bug #46319 workaround)
434 old_val = conf.target.style.marginRight;
435 conf.target.style.marginRight = "1px";
436 c_flush_timer = null;
437 setTimeout(function () {
438 conf.target.style.marginRight = old_val;
439 }, 1);
440 };
441
442 that.setFillColor = function(color) {
443 var rgb, newStyle;
444 if (conf.true_color) {
445 rgb = color;
446 } else {
447 rgb = conf.colourMap[color[0]];
448 }
449 newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
450 if (newStyle !== c_prevStyle) {
451 conf.ctx.fillStyle = newStyle;
452 c_prevStyle = newStyle;
453 }
454 };
455
456 that.fillRect = function(x, y, width, height, color) {
457 that.setFillColor(color);
458 conf.ctx.fillRect(x, y, width, height);
459 };
460
461 that.copyImage = function(old_x, old_y, new_x, new_y, width, height) {
462 conf.ctx.drawImage(conf.target, old_x, old_y, width, height,
463 new_x, new_y, width, height);
464 };
465
466 /*
467 * Tile rendering functions optimized for rendering engines.
468 *
469 * - In Chrome/webkit, Javascript image data array manipulations are
470 * faster than direct Canvas fillStyle, fillRect rendering. In
471 * gecko, Javascript array handling is much slower.
472 */
473 that.getTile = function(x, y, width, height, color) {
474 var img, data = [], p, rgb, red, green, blue, j, i;
475 img = {'x': x, 'y': y, 'width': width, 'height': height,
476 'data': data};
477 if (conf.prefer_js) {
478 if (conf.true_color) {
479 rgb = color;
480 } else {
481 rgb = conf.colourMap[color[0]];
482 }
483 red = rgb[0];
484 green = rgb[1];
485 blue = rgb[2];
486 for (i = 0; i < (width * height * 4); i+=4) {
487 data[i ] = red;
488 data[i + 1] = green;
489 data[i + 2] = blue;
490 }
491 } else {
492 that.fillRect(x, y, width, height, color);
493 }
494 return img;
495 };
496
497 that.setSubTile = function(img, x, y, w, h, color) {
498 var data, p, rgb, red, green, blue, width, j, i, xend, yend;
499 if (conf.prefer_js) {
500 data = img.data;
501 width = img.width;
502 if (conf.true_color) {
503 rgb = color;
504 } else {
505 rgb = conf.colourMap[color[0]];
506 }
507 red = rgb[0];
508 green = rgb[1];
509 blue = rgb[2];
510 xend = x + w;
511 yend = y + h;
512 for (j = y; j < yend; j += 1) {
513 for (i = x; i < xend; i += 1) {
514 p = (i + (j * width) ) * 4;
515 data[p ] = red;
516 data[p + 1] = green;
517 data[p + 2] = blue;
518 }
519 }
520 } else {
521 that.fillRect(img.x + x, img.y + y, w, h, color);
522 }
523 };
524
525 that.putTile = function(img) {
526 if (conf.prefer_js) {
527 that.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
528 } else {
529 // No-op, under gecko already done by setSubTile
530 }
531 };
532
533 that.imageDataGet = function(width, height) {
534 return conf.ctx.getImageData(0, 0, width, height);
535 };
536 that.imageDataCreate = function(width, height) {
537 return conf.ctx.createImageData(width, height);
538 };
539
540 that.rgbxImageData = function(x, y, width, height, arr, offset) {
541 var img, i, j, data;
542 img = that.imageData(width, height);
543 data = img.data;
544 for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
545 data[i + 0] = arr[j + 0];
546 data[i + 1] = arr[j + 1];
547 data[i + 2] = arr[j + 2];
548 data[i + 3] = 255; // Set Alpha
549 }
550 conf.ctx.putImageData(img, x, y);
551 };
552
553 // really slow fallback if we don't have imageData
554 that.rgbxImageFill = function(x, y, width, height, arr, offset) {
555 var i, j, sx = 0, sy = 0;
556 for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
557 that.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]);
558 sx += 1;
559 if ((sx % width) === 0) {
560 sx = 0;
561 sy += 1;
562 }
563 }
564 };
565
566 that.cmapImageData = function(x, y, width, height, arr, offset) {
567 var img, i, j, data, rgb, cmap;
568 img = that.imageData(width, height);
569 data = img.data;
570 cmap = conf.colourMap;
571 for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
572 rgb = cmap[arr[j]];
573 data[i + 0] = rgb[0];
574 data[i + 1] = rgb[1];
575 data[i + 2] = rgb[2];
576 data[i + 3] = 255; // Set Alpha
577 }
578 conf.ctx.putImageData(img, x, y);
579 };
580
581 that.cmapImageFill = function(x, y, width, height, arr, offset) {
582 var i, j, sx = 0, sy = 0, cmap;
583 cmap = conf.colourMap;
584 for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
585 that.fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
586 sx += 1;
587 if ((sx % width) === 0) {
588 sx = 0;
589 sy += 1;
590 }
591 }
592 };
593
594
595 that.blitImage = function(x, y, width, height, arr, offset) {
596 if (conf.true_color) {
597 that.rgbxImage(x, y, width, height, arr, offset);
598 } else {
599 that.cmapImage(x, y, width, height, arr, offset);
600 }
601 };
602
603 that.blitStringImage = function(str, x, y) {
604 var img = new Image();
605 img.onload = function () { conf.ctx.drawImage(img, x, y); };
606 img.src = str;
607 };
608
609 that.changeCursor = function(pixels, mask, hotx, hoty, w, h) {
610 if (conf.cursor_uri === false) {
611 Util.Warn("changeCursor called but no cursor data URI support");
612 return;
613 }
614
615 if (conf.true_color) {
616 changeCursor(conf.target, pixels, mask, hotx, hoty, w, h);
617 } else {
618 changeCursor(conf.target, pixels, mask, hotx, hoty, w, h, conf.colourMap);
619 }
620 };
621
622 return constructor(); // Return the public API interface
623
624 } // End of Canvas()
625
626
627 /* Set CSS cursor property using data URI encoded cursor file */
628 function changeCursor(target, pixels, mask, hotx, hoty, w, h, cmap) {
629 var cur = [], rgb, IHDRsz, RGBsz, ANDsz, XORsz, url, idx, alpha, x, y;
630 //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h);
631
632 // Push multi-byte little-endian values
633 cur.push16le = function (num) {
634 this.push((num ) & 0xFF,
635 (num >> 8) & 0xFF );
636 };
637 cur.push32le = function (num) {
638 this.push((num ) & 0xFF,
639 (num >> 8) & 0xFF,
640 (num >> 16) & 0xFF,
641 (num >> 24) & 0xFF );
642 };
643
644 IHDRsz = 40;
645 RGBsz = w * h * 4;
646 XORsz = Math.ceil( (w * h) / 8.0 );
647 ANDsz = Math.ceil( (w * h) / 8.0 );
648
649 // Main header
650 cur.push16le(0); // 0: Reserved
651 cur.push16le(2); // 2: .CUR type
652 cur.push16le(1); // 4: Number of images, 1 for non-animated ico
653
654 // Cursor #1 header (ICONDIRENTRY)
655 cur.push(w); // 6: width
656 cur.push(h); // 7: height
657 cur.push(0); // 8: colors, 0 -> true-color
658 cur.push(0); // 9: reserved
659 cur.push16le(hotx); // 10: hotspot x coordinate
660 cur.push16le(hoty); // 12: hotspot y coordinate
661 cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz);
662 // 14: cursor data byte size
663 cur.push32le(22); // 18: offset of cursor data in the file
664
665
666 // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO)
667 cur.push32le(IHDRsz); // 22: Infoheader size
668 cur.push32le(w); // 26: Cursor width
669 cur.push32le(h*2); // 30: XOR+AND height
670 cur.push16le(1); // 34: number of planes
671 cur.push16le(32); // 36: bits per pixel
672 cur.push32le(0); // 38: Type of compression
673
674 cur.push32le(XORsz + ANDsz); // 43: Size of Image
675 // Gimp leaves this as 0
676
677 cur.push32le(0); // 46: reserved
678 cur.push32le(0); // 50: reserved
679 cur.push32le(0); // 54: reserved
680 cur.push32le(0); // 58: reserved
681
682 // 62: color data (RGBQUAD icColors[])
683 for (y = h-1; y >= 0; y -= 1) {
684 for (x = 0; x < w; x += 1) {
685 idx = y * Math.ceil(w / 8) + Math.floor(x/8);
686 alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
687
688 if (cmap) {
689 idx = (w * y) + x;
690 rgb = cmap[pixels[idx]];
691 cur.push(rgb[2]); // blue
692 cur.push(rgb[1]); // green
693 cur.push(rgb[0]); // red
694 cur.push(alpha); // alpha
695 } else {
696 idx = ((w * y) + x) * 4;
697 cur.push(pixels[idx + 2]); // blue
698 cur.push(pixels[idx + 1]); // green
699 cur.push(pixels[idx + 0]); // red
700 cur.push(alpha); // alpha
701 }
702 }
703 }
704
705 // XOR/bitmask data (BYTE icXOR[])
706 // (ignored, just needs to be right size)
707 for (y = 0; y < h; y += 1) {
708 for (x = 0; x < Math.ceil(w / 8); x += 1) {
709 cur.push(0x00);
710 }
711 }
712
713 // AND/bitmask data (BYTE icAND[])
714 // (ignored, just needs to be right size)
715 for (y = 0; y < h; y += 1) {
716 for (x = 0; x < Math.ceil(w / 8); x += 1) {
717 cur.push(0x00);
718 }
719 }
720
721 url = "data:image/x-icon;base64," + Base64.encode(cur);
722 target.style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default";
723 //Util.Debug("<< changeCursor, cur.length: " + cur.length);
724 }
725
726
727
728 /* Translate DOM key down/up event to keysym value */
729 function getKeysym(e) {
730 var evt, keysym;
731 evt = (e ? e : window.event);
732
733 /* Remap modifier and special keys */
734 switch ( evt.keyCode ) {
735 case 8 : keysym = 0xFF08; break; // BACKSPACE
736 case 9 : keysym = 0xFF09; break; // TAB
737 case 13 : keysym = 0xFF0D; break; // ENTER
738 case 27 : keysym = 0xFF1B; break; // ESCAPE
739 case 45 : keysym = 0xFF63; break; // INSERT
740 case 46 : keysym = 0xFFFF; break; // DELETE
741 case 36 : keysym = 0xFF50; break; // HOME
742 case 35 : keysym = 0xFF57; break; // END
743 case 33 : keysym = 0xFF55; break; // PAGE_UP
744 case 34 : keysym = 0xFF56; break; // PAGE_DOWN
745 case 37 : keysym = 0xFF51; break; // LEFT
746 case 38 : keysym = 0xFF52; break; // UP
747 case 39 : keysym = 0xFF53; break; // RIGHT
748 case 40 : keysym = 0xFF54; break; // DOWN
749 case 112 : keysym = 0xFFBE; break; // F1
750 case 113 : keysym = 0xFFBF; break; // F2
751 case 114 : keysym = 0xFFC0; break; // F3
752 case 115 : keysym = 0xFFC1; break; // F4
753 case 116 : keysym = 0xFFC2; break; // F5
754 case 117 : keysym = 0xFFC3; break; // F6
755 case 118 : keysym = 0xFFC4; break; // F7
756 case 119 : keysym = 0xFFC5; break; // F8
757 case 120 : keysym = 0xFFC6; break; // F9
758 case 121 : keysym = 0xFFC7; break; // F10
759 case 122 : keysym = 0xFFC8; break; // F11
760 case 123 : keysym = 0xFFC9; break; // F12
761 case 16 : keysym = 0xFFE1; break; // SHIFT
762 case 17 : keysym = 0xFFE3; break; // CONTROL
763 //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
764 case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
765 default : keysym = evt.keyCode; break;
766 }
767
768 /* Remap symbols */
769 switch (keysym) {
770 case 186 : keysym = 59; break; // ; (IE)
771 case 187 : keysym = 61; break; // = (IE)
772 case 188 : keysym = 44; break; // , (Mozilla, IE)
773 case 109 : // - (Mozilla, Opera)
774 if (Util.Engine.gecko || Util.Engine.presto) {
775 keysym = 45; }
776 break;
777 case 189 : keysym = 45; break; // - (IE)
778 case 190 : keysym = 46; break; // . (Mozilla, IE)
779 case 191 : keysym = 47; break; // / (Mozilla, IE)
780 case 192 : keysym = 96; break; // ` (Mozilla, IE)
781 case 219 : keysym = 91; break; // [ (Mozilla, IE)
782 case 220 : keysym = 92; break; // \ (Mozilla, IE)
783 case 221 : keysym = 93; break; // ] (Mozilla, IE)
784 case 222 : keysym = 39; break; // ' (Mozilla, IE)
785 }
786
787 /* Remap shifted and unshifted keys */
788 if (!!evt.shiftKey) {
789 switch (keysym) {
790 case 48 : keysym = 41 ; break; // ) (shifted 0)
791 case 49 : keysym = 33 ; break; // ! (shifted 1)
792 case 50 : keysym = 64 ; break; // @ (shifted 2)
793 case 51 : keysym = 35 ; break; // # (shifted 3)
794 case 52 : keysym = 36 ; break; // $ (shifted 4)
795 case 53 : keysym = 37 ; break; // % (shifted 5)
796 case 54 : keysym = 94 ; break; // ^ (shifted 6)
797 case 55 : keysym = 38 ; break; // & (shifted 7)
798 case 56 : keysym = 42 ; break; // * (shifted 8)
799 case 57 : keysym = 40 ; break; // ( (shifted 9)
800
801 case 59 : keysym = 58 ; break; // : (shifted `)
802 case 61 : keysym = 43 ; break; // + (shifted ;)
803 case 44 : keysym = 60 ; break; // < (shifted ,)
804 case 45 : keysym = 95 ; break; // _ (shifted -)
805 case 46 : keysym = 62 ; break; // > (shifted .)
806 case 47 : keysym = 63 ; break; // ? (shifted /)
807 case 96 : keysym = 126; break; // ~ (shifted `)
808 case 91 : keysym = 123; break; // { (shifted [)
809 case 92 : keysym = 124; break; // | (shifted \)
810 case 93 : keysym = 125; break; // } (shifted ])
811 case 39 : keysym = 34 ; break; // " (shifted ')
812 }
813 } else if ((keysym >= 65) && (keysym <=90)) {
814 /* Remap unshifted A-Z */
815 keysym += 32;
816 } else if (evt.keyLocation === 3) {
817 // numpad keys
818 switch (keysym) {
819 case 96 : keysym = 48; break; // 0
820 case 97 : keysym = 49; break; // 1
821 case 98 : keysym = 50; break; // 2
822 case 99 : keysym = 51; break; // 3
823 case 100: keysym = 52; break; // 4
824 case 101: keysym = 53; break; // 5
825 case 102: keysym = 54; break; // 6
826 case 103: keysym = 55; break; // 7
827 case 104: keysym = 56; break; // 8
828 case 105: keysym = 57; break; // 9
829 case 109: keysym = 45; break; // -
830 case 110: keysym = 46; break; // .
831 case 111: keysym = 47; break; // /
832 }
833 }
834
835 return keysym;
836 }
837