]>
git.proxmox.com Git - mirror_novnc.git/blob - include/canvas.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.txt)
6 * See README.md for usage and integration instructions.
9 /*jslint browser: true, white: false, bitwise: false */
10 /*global window, Util, Base64, changeCursor, getKeysym */
12 function Canvas(conf
) {
15 conf
= conf
|| {}; // Configuration
16 var that
= {}, // Public API interface
18 // Private Canvas namespace variables
19 c_forceCanvas
= false,
33 // Configuration settings
34 function cdef(v
, type
, defval
, desc
) {
35 Util
.conf_default(conf
, that
, v
, type
, defval
, desc
); }
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');
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');
48 cdef('render_mode', 'str', '', 'Canvas rendering mode (read-only)');
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");
60 that
.get_colourMap = function(idx
) {
61 if (typeof idx
=== 'undefined') {
62 return conf
.colourMap
;
64 return conf
.colourMap
[idx
];
68 that
.set_colourMap = function(val
, idx
) {
69 if (typeof idx
=== 'undefined') {
72 conf
.colourMap
[idx
] = val
;
76 that
.set_render_mode = function () { throw("render_mode is read-only"); };
78 // Add some other getters/setters
79 that
.get_width = function() {
82 that
.get_height = function() {
90 // Create the public API interface
91 function constructor() {
92 Util
.Debug(">> Canvas.init");
94 var c
, ctx
, func
, origfunc
, imgTest
, tval
, i
, curDat
, curSave
,
95 has_imageData
= false, UE
= Util
.Engine
;
97 if (! conf
.target
) { throw("target must be set"); }
99 if (typeof conf
.target
=== 'string') {
100 throw("target must be a DOM element");
105 if (! c
.getContext
) { throw("no getContext method"); }
107 if (! conf
.ctx
) { conf
.ctx
= c
.getContext('2d'); }
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
); }
119 * Determine browser Canvas feature support
120 * and select fastest rendering methods
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];
130 has_imageData
= true;
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
;
148 Util
.Info("Prefering javascript operations");
149 if (conf
.prefer_js
=== null) {
150 conf
.prefer_js
= true;
152 that
.rgbxImage
= that
.rgbxImageData
;
153 that
.cmapImage
= that
.cmapImageData
;
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
;
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");
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);
174 myfunc
.apply(this, arguments
);
175 if (!c_flush_timer
) {
176 c_flush_timer
= setTimeout(that
.flush
, 100);
184 * Determine browser support for setting the cursor via data URI
188 for (i
=0; i
< 8 * 8 * 4; i
+= 1) {
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;
198 Util
.Info("Data URI scheme cursor supported");
200 if (conf
.cursor_uri
=== null) {
201 conf
.cursor_uri
= false;
203 Util
.Warn("Data URI scheme cursor not supported");
205 c
.style
.cursor
= curSave
;
207 Util
.Error("Data URI scheme cursor test exception: " + exc2
);
208 conf
.cursor_uri
= false;
213 Util
.Debug("<< Canvas.init");
217 function onMouseButton(e
, down
) {
219 if (! conf
.focused
) {
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);
227 c_mouseButton(pos
.x
, pos
.y
, down
, bmask
);
233 function onMouseDown(e
) {
237 function onMouseUp(e
) {
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;
251 //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
253 c_mouseButton(pos
.x
, pos
.y
, 1, bmask
);
254 c_mouseButton(pos
.x
, pos
.y
, 0, bmask
);
260 function onMouseMove(e
) {
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);
266 c_mouseMove(pos
.x
, pos
.y
);
270 function onKeyDown(e
) {
271 //Util.Debug("keydown: " + getKeysym(e));
272 if (! conf
.focused
) {
276 c_keyPress(getKeysym(e
), 1, e
.ctrlKey
, e
.shiftKey
, e
.altKey
);
282 function onKeyUp(e
) {
283 //Util.Debug("keyup: " + getKeysym(e));
284 if (! conf
.focused
) {
288 c_keyPress(getKeysym(e
), 0, e
.ctrlKey
, e
.shiftKey
, e
.altKey
);
294 function onKeyPress(e
) {
295 //Util.Debug("keypress: " + e.charCode);
296 if (! conf
.focused
) {
299 // Stop keypress events. Necessary for Opera because stopping
300 // keydown and keyup events still results in a keypress event.
305 function onMouseDisable(e
) {
307 if (! conf
.focused
) {
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");
319 //Util.Debug("mouse event not disabled");
324 // Public API interface functions
327 that
.getContext = function () {
331 that
.start = function(keyPressFunc
, mouseButtonFunc
, mouseMoveFunc
) {
333 Util
.Debug(">> Canvas.start");
336 c_keyPress
= keyPressFunc
|| null;
337 c_mouseButton
= mouseButtonFunc
|| null;
338 c_mouseMove
= mouseMoveFunc
|| null;
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',
349 /* Work around right and middle click browser behaviors */
350 Util
.addEvent(conf
.focusContainer
, 'click', onMouseDisable
);
351 Util
.addEvent(conf
.focusContainer
.body
, 'contextmenu', onMouseDisable
);
353 Util
.Debug("<< Canvas.start");
356 that
.stop = function() {
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',
367 /* Work around right and middle click browser behaviors */
368 Util
.removeEvent(conf
.focusContainer
, 'click', onMouseDisable
);
369 Util
.removeEvent(conf
.focusContainer
.body
, 'contextmenu', onMouseDisable
);
371 // Turn off cursor rendering
372 if (conf
.cursor_uri
) {
373 c
.style
.cursor
= "default";
377 that
.rescale = function(factor
) {
379 properties
= ['transform', 'WebkitTransform', 'MozTransform', null];
381 tp
= properties
.shift();
383 if (typeof c
.style
[tp
] !== 'undefined') {
386 tp
= properties
.shift();
390 Util
.Debug("No scaling support");
394 if (conf
.scale
=== factor
) {
395 //Util.Debug("Canvas already scaled to '" + 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)";
405 that
.resize = function(width
, height
, true_color
) {
408 if (typeof true_color
!== "undefined") {
409 conf
.true_color
= true_color
;
416 c_width
= c
.offsetWidth
;
417 c_height
= c
.offsetHeight
;
419 that
.rescale(conf
.scale
);
422 that
.clear = function() {
423 that
.resize(640, 20);
424 conf
.ctx
.clearRect(0, 0, c_width
, c_height
);
426 // No benefit over default ("source-over") in Chrome and firefox
427 //conf.ctx.globalCompositeOperation = "copy";
430 that
.flush = function() {
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
;
442 that
.setFillColor = function(color
) {
444 if (conf
.true_color
) {
447 rgb
= conf
.colourMap
[color
[0]];
449 newStyle
= "rgb(" + rgb
[0] + "," + rgb
[1] + "," + rgb
[2] + ")";
450 if (newStyle
!== c_prevStyle
) {
451 conf
.ctx
.fillStyle
= newStyle
;
452 c_prevStyle
= newStyle
;
456 that
.fillRect = function(x
, y
, width
, height
, color
) {
457 that
.setFillColor(color
);
458 conf
.ctx
.fillRect(x
, y
, width
, height
);
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
);
467 * Tile rendering functions optimized for rendering engines.
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.
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
,
477 if (conf
.prefer_js
) {
478 if (conf
.true_color
) {
481 rgb
= conf
.colourMap
[color
[0]];
486 for (i
= 0; i
< (width
* height
* 4); i
+=4) {
492 that
.fillRect(x
, y
, width
, height
, color
);
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
) {
502 if (conf
.true_color
) {
505 rgb
= conf
.colourMap
[color
[0]];
512 for (j
= y
; j
< yend
; j
+= 1) {
513 for (i
= x
; i
< xend
; i
+= 1) {
514 p
= (i
+ (j
* width
) ) * 4;
521 that
.fillRect(img
.x
+ x
, img
.y
+ y
, w
, h
, color
);
525 that
.putTile = function(img
) {
526 if (conf
.prefer_js
) {
527 that
.rgbxImage(img
.x
, img
.y
, img
.width
, img
.height
, img
.data
, 0);
529 // No-op, under gecko already done by setSubTile
533 that
.imageDataGet = function(width
, height
) {
534 return conf
.ctx
.getImageData(0, 0, width
, height
);
536 that
.imageDataCreate = function(width
, height
) {
537 return conf
.ctx
.createImageData(width
, height
);
540 that
.rgbxImageData = function(x
, y
, width
, height
, arr
, offset
) {
542 img
= that
.imageData(width
, height
);
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
550 conf
.ctx
.putImageData(img
, x
, y
);
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]]);
559 if ((sx
% width
) === 0) {
566 that
.cmapImageData = function(x
, y
, width
, height
, arr
, offset
) {
567 var img
, i
, j
, data
, rgb
, cmap
;
568 img
= that
.imageData(width
, height
);
570 cmap
= conf
.colourMap
;
571 for (i
=0, j
=offset
; i
< (width
* height
* 4); i
+=4, j
+=1) {
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
578 conf
.ctx
.putImageData(img
, x
, y
);
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
]]);
587 if ((sx
% width
) === 0) {
595 that
.blitImage = function(x
, y
, width
, height
, arr
, offset
) {
596 if (conf
.true_color
) {
597 that
.rgbxImage(x
, y
, width
, height
, arr
, offset
);
599 that
.cmapImage(x
, y
, width
, height
, arr
, offset
);
603 that
.blitStringImage = function(str
, x
, y
) {
604 var img
= new Image();
605 img
.onload = function () { conf
.ctx
.drawImage(img
, x
, y
); };
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");
615 if (conf
.true_color
) {
616 changeCursor(conf
.target
, pixels
, mask
, hotx
, hoty
, w
, h
);
618 changeCursor(conf
.target
, pixels
, mask
, hotx
, hoty
, w
, h
, conf
.colourMap
);
622 return constructor(); // Return the public API interface
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);
632 // Push multi-byte little-endian values
633 cur
.push16le = function (num
) {
634 this.push((num
) & 0xFF,
637 cur
.push32le = function (num
) {
638 this.push((num
) & 0xFF,
641 (num
>> 24) & 0xFF );
646 XORsz
= Math
.ceil( (w
* h
) / 8.0 );
647 ANDsz
= Math
.ceil( (w
* h
) / 8.0 );
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
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
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
674 cur
.push32le(XORsz
+ ANDsz
); // 43: Size of Image
675 // Gimp leaves this as 0
677 cur
.push32le(0); // 46: reserved
678 cur
.push32le(0); // 50: reserved
679 cur
.push32le(0); // 54: reserved
680 cur
.push32le(0); // 58: reserved
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;
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
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
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) {
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) {
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);
728 /* Translate DOM key down/up event to keysym value */
729 function getKeysym(e) {
731 evt = (e ? e : window.event);
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;
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
) {
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)
787 /* Remap shifted and unshifted keys */
788 if (!!evt
.shiftKey
) {
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)
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 ')
813 } else if ((keysym
>= 65) && (keysym
<=90)) {
814 /* Remap unshifted A-Z */
816 } else if (evt
.keyLocation
=== 3) {
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; // /