]> git.proxmox.com Git - mirror_novnc.git/blame - include/canvas.js
web-socket-js event fixes.
[mirror_novnc.git] / include / canvas.js
CommitLineData
c4164bda
JM
1/*
2 * noVNC: HTML5 VNC client
af6b17ce
JM
3 * Copyright (C) 2010 Joel Martin
4 * Licensed under LGPL-3 (see LICENSE.LGPL-3)
c4164bda
JM
5 *
6 * See README.md for usage and integration instructions.
7 */
c4164bda 8
15046f00
JM
9"use strict";
10/*jslint white: false, bitwise: false */
11/*global window, console, $, Util */
c4164bda 12
d93d3e09
JM
13var Canvas, Canvas_native;
14
15(function () {
48eed1ac 16 var pre, start = "<script src='", end = "'><\/script>";
d93d3e09
JM
17 if (document.createElement('canvas').getContext) {
18 Canvas_native = true;
19 } else {
48eed1ac
JM
20 pre = (typeof VNC_uri_prefix !== "undefined") ?
21 VNC_uri_prefix : "include/";
22 document.write(start + pre + "excanvas.js" + end);
d93d3e09 23 Canvas_native = false;
d93d3e09
JM
24 }
25}());
26
c4164bda 27// Everything namespaced inside Canvas
d93d3e09 28Canvas = {
c8460b03 29
d93d3e09 30prefer_js : false,
5235b29d 31force_canvas : false,
97763d0e 32
d41c33e4
JM
33true_color : false,
34colourMap : [],
35
c8460b03
JM
36c_wx : 0,
37c_wy : 0,
64ab5c4d 38ctx : null,
c8460b03 39
48ebcdb1
JM
40prevStyle: "",
41
15046f00 42focused : true,
e2e7c224
JM
43keyPress : null,
44mouseButton : null,
45mouseMove : null,
46
e2e7c224 47onMouseButton: function(e, down) {
61dd52c9 48 var evt, pos, bmask;
15046f00
JM
49 evt = (e ? e : window.event);
50 pos = Util.getEventPosition(e, $(Canvas.id));
e2e7c224 51 bmask = 1 << evt.button;
15046f00 52 //console.log('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
e2e7c224 53 if (Canvas.mouseButton) {
61dd52c9 54 Canvas.mouseButton(pos.x, pos.y, down, bmask);
e2e7c224 55 }
15046f00 56 Util.stopEvent(e);
e2e7c224 57 return false;
c8460b03 58},
f272267b 59
e2e7c224
JM
60onMouseDown: function (e) {
61 Canvas.onMouseButton(e, 1);
c8460b03 62},
f272267b 63
e2e7c224
JM
64onMouseUp: function (e) {
65 Canvas.onMouseButton(e, 0);
8cf20615
JM
66},
67
e2e7c224 68onMouseWheel: function (e) {
15046f00
JM
69 var evt, pos, bmask, wheelData;
70 evt = (e ? e : window.event);
71 pos = Util.getEventPosition(e, $(Canvas.id));
72 wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
e2e7c224
JM
73 if (wheelData > 0) {
74 bmask = 1 << 3;
75 } else {
76 bmask = 1 << 4;
77 }
61dd52c9 78 //console.log('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
e2e7c224 79 if (Canvas.mouseButton) {
61dd52c9
JM
80 Canvas.mouseButton(pos.x, pos.y, 1, bmask);
81 Canvas.mouseButton(pos.x, pos.y, 0, bmask);
e2e7c224 82 }
15046f00 83 Util.stopEvent(e);
e2e7c224 84 return false;
a575a383
JM
85},
86
8cf20615 87
e2e7c224 88onMouseMove: function (e) {
61dd52c9 89 var evt, pos;
15046f00
JM
90 evt = (e ? e : window.event);
91 pos = Util.getEventPosition(e, $(Canvas.id));
61dd52c9
JM
92 //console.log('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
93 if (Canvas.mouseMove) {
94 Canvas.mouseMove(pos.x, pos.y);
e2e7c224
JM
95 }
96},
97
98onKeyDown: function (e) {
15046f00
JM
99 //console.log("keydown: " + Canvas.getKeysym(e));
100 if (! Canvas.focused) {
101 return true;
102 }
e2e7c224
JM
103 if (Canvas.keyPress) {
104 Canvas.keyPress(Canvas.getKeysym(e), 1);
105 }
15046f00 106 Util.stopEvent(e);
e2e7c224 107 return false;
c8460b03 108},
f272267b 109
e2e7c224 110onKeyUp : function (e) {
d93d3e09 111 //console.log("keyup: " + Canvas.getKeysym(e));
15046f00
JM
112 if (! Canvas.focused) {
113 return true;
114 }
e2e7c224
JM
115 if (Canvas.keyPress) {
116 Canvas.keyPress(Canvas.getKeysym(e), 0);
117 }
15046f00 118 Util.stopEvent(e);
e2e7c224 119 return false;
c8460b03 120},
f272267b 121
e2e7c224 122onMouseDisable: function (e) {
61dd52c9 123 var evt, pos;
15046f00
JM
124 evt = (e ? e : window.event);
125 pos = Util.getPosition($(Canvas.id));
f272267b 126 /* Stop propagation if inside canvas area */
61dd52c9
JM
127 if ((evt.clientX >= pos.x) &&
128 (evt.clientY >= pos.y) &&
129 (evt.clientX < (pos.x + Canvas.c_wx)) &&
130 (evt.clientY < (pos.y + Canvas.c_wy))) {
15046f00
JM
131 //console.log("mouse event disabled");
132 Util.stopEvent(e);
f272267b 133 return false;
c4164bda 134 }
15046f00
JM
135 //console.log("mouse event not disabled");
136 return true;
c8460b03 137},
f272267b
JM
138
139
d93d3e09
JM
140init: function (id) {
141 var c, imgTest, arora;
15046f00 142 console.log(">> Canvas.init");
f272267b 143
532a9fd9 144 Canvas.id = id;
d93d3e09
JM
145 c = $(Canvas.id);
146
147 if (Canvas_native) {
148 console.log("Using native canvas");
149 // Use default Canvas functions
150 } else {
151 console.warn("Using excanvas canvas emulation");
48eed1ac 152 //G_vmlCanvasManager.init(c);
d93d3e09
JM
153 G_vmlCanvasManager.initElement(c);
154 }
155
156 if (! c.getContext) { throw("No getContext method"); }
157 Canvas.ctx = c.getContext('2d');
158
159 Canvas.clear();
160
d93d3e09 161 /*
48eed1ac
JM
162 * Determine browser Canvas feature support
163 * and select fastest rendering methods
d93d3e09
JM
164 */
165 tval = 0;
48eed1ac 166 Canvas.has_imageData = false;
d93d3e09
JM
167 try {
168 imgTest = Canvas.ctx.getImageData(0, 0, 1,1);
169 imgTest.data[0] = 123;
170 imgTest.data[3] = 255;
171 Canvas.ctx.putImageData(imgTest, 0, 0);
172 tval = Canvas.ctx.getImageData(0, 0, 1, 1).data[0];
48eed1ac
JM
173 if (tval === 123) {
174 Canvas.has_imageData = true;
175 }
176 } catch (exc) {}
177
178 if (Canvas.has_imageData) {
179 console.log("Canvas supports imageData");
5235b29d 180 Canvas.force_canvas = false;
d93d3e09
JM
181 if (Canvas.ctx.createImageData) {
182 // If it's there, it's faster
183 console.log("Using Canvas createImageData");
184 Canvas._imageData = Canvas._imageDataCreate;
185 } else if (Canvas.ctx.getImageData) {
186 console.log("Using Canvas getImageData");
187 Canvas._imageData = Canvas._imageDataGet;
d93d3e09 188 }
67134184
JM
189 console.log("Prefering javascript operations");
190 Canvas.prefer_js = true;
5235b29d
JM
191 Canvas._rgbxImage = Canvas._rgbxImageData;
192 Canvas._cmapImage = Canvas._cmapImageData;
d93d3e09 193 } else {
48eed1ac 194 console.log("Canvas lacks imageData, using fillRect (slow)");
5235b29d
JM
195 Canvas.force_canvas = true;
196 Canvas.prefer_js = false;
d93d3e09
JM
197 Canvas._rgbxImage = Canvas._rgbxImageFill;
198 Canvas._cmapImage = Canvas._cmapImageFill;
d93d3e09 199 }
532a9fd9 200
d93d3e09
JM
201 Canvas.colourMap = [];
202 Canvas.prevStyle = "";
203 Canvas.focused = true;
204
205 //console.log("<< Canvas.init");
206 return true;
207},
208
209
210start: function (keyPress, mouseButton, mouseMove) {
211 var c;
212 console.log(">> Canvas.start");
213
214 c = $(Canvas.id);
e2e7c224
JM
215 Canvas.keyPress = keyPress || null;
216 Canvas.mouseButton = mouseButton || null;
217 Canvas.mouseMove = mouseMove || null;
64ab5c4d 218
15046f00
JM
219 Util.addEvent(document, 'keydown', Canvas.onKeyDown);
220 Util.addEvent(document, 'keyup', Canvas.onKeyUp);
221 Util.addEvent(c, 'mousedown', Canvas.onMouseDown);
222 Util.addEvent(c, 'mouseup', Canvas.onMouseUp);
223 Util.addEvent(c, 'mousemove', Canvas.onMouseMove);
224 Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
225 Canvas.onMouseWheel);
f272267b
JM
226
227 /* Work around right and middle click browser behaviors */
15046f00
JM
228 Util.addEvent(document, 'click', Canvas.onMouseDisable);
229 Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
f272267b 230
d93d3e09 231 //console.log("<< Canvas.start");
532a9fd9
JM
232},
233
234clear: function () {
4b4496ad 235 Canvas.resize(640, 20);
d93d3e09 236 Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy);
4b4496ad
JM
237},
238
d93d3e09 239resize: function (width, height, true_color) {
31af85b9 240 var c = $(Canvas.id);
d93d3e09
JM
241
242 if (typeof true_color !== "undefined") {
243 Canvas.true_color = true_color;
244 }
245
4b4496ad
JM
246 c.width = width;
247 c.height = height;
d93d3e09
JM
248
249 Canvas.c_wx = c.offsetWidth;
250 Canvas.c_wy = c.offsetHeight;
31af85b9
JM
251},
252
253stop: function () {
532a9fd9 254 var c = $(Canvas.id);
15046f00
JM
255 Util.removeEvent(document, 'keydown', Canvas.onKeyDown);
256 Util.removeEvent(document, 'keyup', Canvas.onKeyUp);
257 Util.removeEvent(c, 'mousedown', Canvas.onMouseDown);
258 Util.removeEvent(c, 'mouseup', Canvas.onMouseUp);
259 Util.removeEvent(c, 'mousemove', Canvas.onMouseMove);
260 Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
261 Canvas.onMouseWheel);
2bcb2d5b
JM
262
263 /* Work around right and middle click browser behaviors */
15046f00
JM
264 Util.removeEvent(document, 'click', Canvas.onMouseDisable);
265 Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
532a9fd9
JM
266},
267
3875f847
JM
268/*
269 * Tile rendering functions optimized for rendering engines.
270 *
271 * - In Chrome/webkit, Javascript image data array manipulations are
272 * faster than direct Canvas fillStyle, fillRect rendering. In
273 * gecko, Javascript array handling is much slower.
274 */
275getTile: function(x, y, width, height, color) {
d41c33e4 276 var img, data, p, rgb, red, green, blue, j, i;
c4164bda
JM
277 img = {'x': x, 'y': y, 'width': width, 'height': height,
278 'data': []};
97763d0e
JM
279 if (Canvas.prefer_js) {
280 data = img.data;
d41c33e4
JM
281 if (Canvas.true_color) {
282 rgb = color;
283 } else {
284 rgb = Canvas.colourMap[color[0]];
285 }
286 red = rgb[0];
287 green = rgb[1];
288 blue = rgb[2];
15046f00
JM
289 for (j = 0; j < height; j += 1) {
290 for (i = 0; i < width; i += 1) {
3875f847 291 p = (i + (j * width) ) * 4;
d41c33e4
JM
292 data[p + 0] = red;
293 data[p + 1] = green;
294 data[p + 2] = blue;
295 //data[p + 3] = 255; // Set Alpha
3875f847
JM
296 }
297 }
298 } else {
299 Canvas.fillRect(x, y, width, height, color);
300 }
301 return img;
302},
303
d93d3e09 304setSubTile: function(img, x, y, w, h, color) {
d41c33e4 305 var data, p, rgb, red, green, blue, width, j, i;
97763d0e
JM
306 if (Canvas.prefer_js) {
307 data = img.data;
c4164bda 308 width = img.width;
d41c33e4
JM
309 if (Canvas.true_color) {
310 rgb = color;
311 } else {
312 rgb = Canvas.colourMap[color[0]];
313 }
314 red = rgb[0];
315 green = rgb[1];
316 blue = rgb[2];
15046f00
JM
317 for (j = 0; j < h; j += 1) {
318 for (i = 0; i < w; i += 1) {
3875f847 319 p = (x + i + ((y + j) * width) ) * 4;
97763d0e
JM
320 data[p + 0] = red;
321 data[p + 1] = green;
322 data[p + 2] = blue;
3875f847
JM
323 //img.data[p + 3] = 255; // Set Alpha
324 }
325 }
326 } else {
327 Canvas.fillRect(img.x + x, img.y + y, w, h, color);
328 }
329},
330
331putTile: function(img) {
97763d0e 332 if (Canvas.prefer_js) {
d93d3e09 333 Canvas._rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
3875f847 334 } else {
d93d3e09 335 // No-op, under gecko already done by setSubTile
3875f847
JM
336 }
337},
338
d93d3e09
JM
339_imageDataGet: function(width, height) {
340 return Canvas.ctx.getImageData(0, 0, width, height);
341},
342_imageDataCreate: function(width, height) {
343 return Canvas.ctx.createImageData(width, height);
344},
345_imageDataRaw: function(width, height) {
346 return {'data': [], 'width': width, 'height': height};
347},
3875f847 348
d93d3e09 349_rgbxImageData: function(x, y, width, height, arr, offset) {
7f4f41b0 350 var img, i, j, data;
d93d3e09 351 img = Canvas._imageData(width, height);
97763d0e 352 data = img.data;
d41c33e4 353 for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
7f4f41b0
JM
354 data[i + 0] = arr[j + 0];
355 data[i + 1] = arr[j + 1];
356 data[i + 2] = arr[j + 2];
357 data[i + 3] = 255; // Set Alpha
64ab5c4d
JM
358 }
359 Canvas.ctx.putImageData(img, x, y);
d41c33e4 360},
64ab5c4d 361
d93d3e09
JM
362// really slow fallback if we don't have imageData
363_rgbxImageFill: function(x, y, width, height, arr, offset) {
364 var sx = 0, sy = 0;
365 for (i=0, j=offset; i < (width * height); i+=1, j+=4) {
366 Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]);
367 sx += 1;
368 if ((sx % width) === 0) {
369 sx = 0;
370 sy += 1;
371 }
372 }
373},
374
375_cmapImageData: function(x, y, width, height, arr, offset) {
15046f00 376 var img, i, j, data, rgb, cmap;
d93d3e09 377 img = Canvas._imageData(width, height);
d41c33e4
JM
378 data = img.data;
379 cmap = Canvas.colourMap;
d93d3e09 380 for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) {
d41c33e4
JM
381 rgb = cmap[arr[j]];
382 data[i + 0] = rgb[0];
383 data[i + 1] = rgb[1];
384 data[i + 2] = rgb[2];
385 data[i + 3] = 255; // Set Alpha
386 }
387 Canvas.ctx.putImageData(img, x, y);
388},
389
d93d3e09
JM
390_cmapImageFill: function(x, y, width, height, arr, offset) {
391 var sx = 0, sy = 0;
392 cmap = Canvas.colourMap;
393 console.log("here1: arr[2]: " + arr[2] + ", cmap[arr[2]]: " + cmap[arr[2]]);
394 for (i=0, j=offset; i < (width * height); i+=1, j+=1) {
395 Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j]]);
396 sx += 1;
397 if ((sx % width) === 0) {
398 sx = 0;
399 sy += 1;
400 }
401 }
402},
403
404
d41c33e4
JM
405blitImage: function(x, y, width, height, arr, offset) {
406 if (Canvas.true_color) {
d93d3e09 407 Canvas._rgbxImage(x, y, width, height, arr, offset);
d41c33e4 408 } else {
d93d3e09 409 Canvas._cmapImage(x, y, width, height, arr, offset);
d41c33e4 410 }
d9cbdc7d
JM
411},
412
d93d3e09
JM
413blitStringImage: function(str, x, y) {
414 var img = new Image();
415 img.onload = function () { Canvas.ctx.drawImage(img, x, y); };
416 img.src = str;
417},
418
419setFillColor: function(color) {
d41c33e4
JM
420 var rgb, newStyle;
421 if (Canvas.true_color) {
422 rgb = color;
423 } else {
424 rgb = Canvas.colourMap[color[0]];
425 }
c4164bda 426 if (newStyle !== Canvas.prevStyle) {
d41c33e4 427 newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
48ebcdb1 428 Canvas.ctx.fillStyle = newStyle;
c4164bda 429 Canvas.prevStyle = newStyle;
48ebcdb1 430 }
d93d3e09
JM
431},
432
433fillRect: function(x, y, width, height, color) {
434 Canvas.setFillColor(color);
ed7e776d
JM
435 Canvas.ctx.fillRect(x, y, width, height);
436},
437
48617e27
JM
438copyImage: function(old_x, old_y, new_x, new_y, width, height) {
439 Canvas.ctx.drawImage($(Canvas.id), old_x, old_y, width, height,
440 new_x, new_y, width, height);
441},
442
d9cbdc7d
JM
443/* Translate DOM key event to keysym value */
444getKeysym: function(e) {
c4164bda 445 var evt, keysym;
15046f00 446 evt = (e ? e : window.event);
d9cbdc7d
JM
447
448 /* Remap modifier and special keys */
449 switch ( evt.keyCode ) {
450 case 8 : keysym = 0xFF08; break; // BACKSPACE
451 case 9 : keysym = 0xFF09; break; // TAB
452 case 13 : keysym = 0xFF0D; break; // ENTER
453 case 27 : keysym = 0xFF1B; break; // ESCAPE
454 case 45 : keysym = 0xFF63; break; // INSERT
455 case 46 : keysym = 0xFFFF; break; // DELETE
456 case 36 : keysym = 0xFF50; break; // HOME
457 case 35 : keysym = 0xFF57; break; // END
458 case 33 : keysym = 0xFF55; break; // PAGE_UP
459 case 34 : keysym = 0xFF56; break; // PAGE_DOWN
460 case 37 : keysym = 0xFF51; break; // LEFT
461 case 38 : keysym = 0xFF52; break; // UP
462 case 39 : keysym = 0xFF53; break; // RIGHT
463 case 40 : keysym = 0xFF54; break; // DOWN
464 case 112 : keysym = 0xFFBE; break; // F1
465 case 113 : keysym = 0xFFBF; break; // F2
466 case 114 : keysym = 0xFFC0; break; // F3
467 case 115 : keysym = 0xFFC1; break; // F4
468 case 116 : keysym = 0xFFC2; break; // F5
469 case 117 : keysym = 0xFFC3; break; // F6
470 case 118 : keysym = 0xFFC4; break; // F7
471 case 119 : keysym = 0xFFC5; break; // F8
472 case 120 : keysym = 0xFFC6; break; // F9
473 case 121 : keysym = 0xFFC7; break; // F10
474 case 122 : keysym = 0xFFC8; break; // F11
475 case 123 : keysym = 0xFFC9; break; // F12
476 case 16 : keysym = 0xFFE1; break; // SHIFT
477 case 17 : keysym = 0xFFE3; break; // CONTROL
2e041cf2
JM
478 //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
479 case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
d9cbdc7d
JM
480 default : keysym = evt.keyCode; break;
481 }
482
483 /* Remap symbols */
484 switch (keysym) {
485 case 186 : keysym = 59; break; // ; (IE)
486 case 187 : keysym = 61; break; // = (IE)
487 case 188 : keysym = 44; break; // , (Mozilla, IE)
9fec75c0 488 case 109 : // - (Mozilla)
15046f00 489 if (Util.Engine.gecko) {
c4164bda
JM
490 keysym = 45; }
491 break;
d9cbdc7d
JM
492 case 189 : keysym = 45; break; // - (IE)
493 case 190 : keysym = 46; break; // . (Mozilla, IE)
494 case 191 : keysym = 47; break; // / (Mozilla, IE)
495 case 192 : keysym = 96; break; // ` (Mozilla, IE)
496 case 219 : keysym = 91; break; // [ (Mozilla, IE)
497 case 220 : keysym = 92; break; // \ (Mozilla, IE)
498 case 221 : keysym = 93; break; // ] (Mozilla, IE)
499 case 222 : keysym = 39; break; // ' (Mozilla, IE)
500 }
501
502 /* Remap shifted and unshifted keys */
503 if (!!evt.shiftKey) {
504 switch (keysym) {
505 case 48 : keysym = 41 ; break; // ) (shifted 0)
506 case 49 : keysym = 33 ; break; // ! (shifted 1)
507 case 50 : keysym = 64 ; break; // @ (shifted 2)
508 case 51 : keysym = 35 ; break; // # (shifted 3)
509 case 52 : keysym = 36 ; break; // $ (shifted 4)
510 case 53 : keysym = 37 ; break; // % (shifted 5)
511 case 54 : keysym = 94 ; break; // ^ (shifted 6)
512 case 55 : keysym = 38 ; break; // & (shifted 7)
513 case 56 : keysym = 42 ; break; // * (shifted 8)
514 case 57 : keysym = 40 ; break; // ( (shifted 9)
515
516 case 59 : keysym = 58 ; break; // : (shifted `)
517 case 61 : keysym = 43 ; break; // + (shifted ;)
518 case 44 : keysym = 60 ; break; // < (shifted ,)
519 case 45 : keysym = 95 ; break; // _ (shifted -)
520 case 46 : keysym = 62 ; break; // > (shifted .)
521 case 47 : keysym = 63 ; break; // ? (shifted /)
522 case 96 : keysym = 126; break; // ~ (shifted `)
523 case 91 : keysym = 123; break; // { (shifted [)
524 case 92 : keysym = 124; break; // | (shifted \)
525 case 93 : keysym = 125; break; // } (shifted ])
526 case 39 : keysym = 34 ; break; // " (shifted ')
527 }
528 } else if ((keysym >= 65) && (keysym <=90)) {
529 /* Remap unshifted A-Z */
530 keysym += 32;
531 }
532
533 return keysym;
f272267b
JM
534}
535
d9cbdc7d 536
c8460b03
JM
537};
538