canvas.js 17 KB

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