canvas.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. // Everything namespaced inside Canvas
  12. var Canvas = {
  13. prefer_js : false,
  14. true_color : false,
  15. colourMap : [],
  16. c_wx : 0,
  17. c_wy : 0,
  18. ctx : null,
  19. prevStyle: "",
  20. focused : true,
  21. keyPress : null,
  22. mouseButton : null,
  23. mouseMove : null,
  24. onMouseButton: function(e, down) {
  25. var evt, pos, bmask;
  26. evt = (e ? e : window.event);
  27. pos = Util.getEventPosition(e, $(Canvas.id));
  28. bmask = 1 << evt.button;
  29. //console.log('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask);
  30. if (Canvas.mouseButton) {
  31. Canvas.mouseButton(pos.x, pos.y, down, bmask);
  32. }
  33. Util.stopEvent(e);
  34. return false;
  35. },
  36. onMouseDown: function (e) {
  37. Canvas.onMouseButton(e, 1);
  38. },
  39. onMouseUp: function (e) {
  40. Canvas.onMouseButton(e, 0);
  41. },
  42. onMouseWheel: function (e) {
  43. var evt, pos, bmask, wheelData;
  44. evt = (e ? e : window.event);
  45. pos = Util.getEventPosition(e, $(Canvas.id));
  46. wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40;
  47. if (wheelData > 0) {
  48. bmask = 1 << 3;
  49. } else {
  50. bmask = 1 << 4;
  51. }
  52. //console.log('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y);
  53. if (Canvas.mouseButton) {
  54. Canvas.mouseButton(pos.x, pos.y, 1, bmask);
  55. Canvas.mouseButton(pos.x, pos.y, 0, bmask);
  56. }
  57. Util.stopEvent(e);
  58. return false;
  59. },
  60. onMouseMove: function (e) {
  61. var evt, pos;
  62. evt = (e ? e : window.event);
  63. pos = Util.getEventPosition(e, $(Canvas.id));
  64. //console.log('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y);
  65. if (Canvas.mouseMove) {
  66. Canvas.mouseMove(pos.x, pos.y);
  67. }
  68. },
  69. onKeyDown: function (e) {
  70. //console.log("keydown: " + Canvas.getKeysym(e));
  71. if (! Canvas.focused) {
  72. return true;
  73. }
  74. if (Canvas.keyPress) {
  75. Canvas.keyPress(Canvas.getKeysym(e), 1);
  76. }
  77. Util.stopEvent(e);
  78. return false;
  79. },
  80. onKeyUp : function (e) {
  81. //console.log("keyup: " + e.key + "(" + e.code + ")");
  82. if (! Canvas.focused) {
  83. return true;
  84. }
  85. if (Canvas.keyPress) {
  86. Canvas.keyPress(Canvas.getKeysym(e), 0);
  87. }
  88. Util.stopEvent(e);
  89. return false;
  90. },
  91. onMouseDisable: function (e) {
  92. var evt, pos;
  93. evt = (e ? e : window.event);
  94. pos = Util.getPosition($(Canvas.id));
  95. /* Stop propagation if inside canvas area */
  96. if ((evt.clientX >= pos.x) &&
  97. (evt.clientY >= pos.y) &&
  98. (evt.clientX < (pos.x + Canvas.c_wx)) &&
  99. (evt.clientY < (pos.y + Canvas.c_wy))) {
  100. //console.log("mouse event disabled");
  101. Util.stopEvent(e);
  102. return false;
  103. }
  104. //console.log("mouse event not disabled");
  105. return true;
  106. },
  107. init: function (id, width, height, true_color, keyPress,
  108. mouseButton, mouseMove) {
  109. console.log(">> Canvas.init");
  110. Canvas.id = id;
  111. Canvas.keyPress = keyPress || null;
  112. Canvas.mouseButton = mouseButton || null;
  113. Canvas.mouseMove = mouseMove || null;
  114. var c = $(Canvas.id);
  115. Util.addEvent(document, 'keydown', Canvas.onKeyDown);
  116. Util.addEvent(document, 'keyup', Canvas.onKeyUp);
  117. Util.addEvent(c, 'mousedown', Canvas.onMouseDown);
  118. Util.addEvent(c, 'mouseup', Canvas.onMouseUp);
  119. Util.addEvent(c, 'mousemove', Canvas.onMouseMove);
  120. Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
  121. Canvas.onMouseWheel);
  122. /* Work around right and middle click browser behaviors */
  123. Util.addEvent(document, 'click', Canvas.onMouseDisable);
  124. Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
  125. Canvas.resize(width, height);
  126. Canvas.c_wx = c.offsetWidth;
  127. Canvas.c_wy = c.offsetHeight;
  128. Canvas.true_color = true_color;
  129. Canvas.colourMap = [];
  130. if (! c.getContext) { return; }
  131. Canvas.ctx = c.getContext('2d');
  132. Canvas.prevStyle = "";
  133. Canvas.focused = true;
  134. if (Util.Engine.webkit) {
  135. Canvas.prefer_js = true;
  136. }
  137. //console.log("<< Canvas.init");
  138. },
  139. clear: function () {
  140. Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy);
  141. Canvas.resize(640, 20);
  142. },
  143. resize: function (width, height) {
  144. var c = $(Canvas.id);
  145. c.width = width;
  146. c.height = height;
  147. },
  148. stop: function () {
  149. var c = $(Canvas.id);
  150. Util.removeEvent(document, 'keydown', Canvas.onKeyDown);
  151. Util.removeEvent(document, 'keyup', Canvas.onKeyUp);
  152. Util.removeEvent(c, 'mousedown', Canvas.onMouseDown);
  153. Util.removeEvent(c, 'mouseup', Canvas.onMouseUp);
  154. Util.removeEvent(c, 'mousemove', Canvas.onMouseMove);
  155. Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel',
  156. Canvas.onMouseWheel);
  157. /* Work around right and middle click browser behaviors */
  158. Util.removeEvent(document, 'click', Canvas.onMouseDisable);
  159. Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable);
  160. },
  161. /*
  162. * Tile rendering functions optimized for rendering engines.
  163. *
  164. * - In Chrome/webkit, Javascript image data array manipulations are
  165. * faster than direct Canvas fillStyle, fillRect rendering. In
  166. * gecko, Javascript array handling is much slower.
  167. */
  168. getTile: function(x, y, width, height, color) {
  169. var img, data, p, rgb, red, green, blue, j, i;
  170. img = {'x': x, 'y': y, 'width': width, 'height': height,
  171. 'data': []};
  172. if (Canvas.prefer_js) {
  173. data = img.data;
  174. if (Canvas.true_color) {
  175. rgb = color;
  176. } else {
  177. rgb = Canvas.colourMap[color[0]];
  178. }
  179. red = rgb[0];
  180. green = rgb[1];
  181. blue = rgb[2];
  182. for (j = 0; j < height; j += 1) {
  183. for (i = 0; i < width; i += 1) {
  184. p = (i + (j * width) ) * 4;
  185. data[p + 0] = red;
  186. data[p + 1] = green;
  187. data[p + 2] = blue;
  188. //data[p + 3] = 255; // Set Alpha
  189. }
  190. }
  191. } else {
  192. Canvas.fillRect(x, y, width, height, color);
  193. }
  194. return img;
  195. },
  196. setTile: function(img, x, y, w, h, color) {
  197. var data, p, rgb, red, green, blue, width, j, i;
  198. if (Canvas.prefer_js) {
  199. data = img.data;
  200. width = img.width;
  201. if (Canvas.true_color) {
  202. rgb = color;
  203. } else {
  204. rgb = Canvas.colourMap[color[0]];
  205. }
  206. red = rgb[0];
  207. green = rgb[1];
  208. blue = rgb[2];
  209. for (j = 0; j < h; j += 1) {
  210. for (i = 0; i < w; i += 1) {
  211. p = (x + i + ((y + j) * width) ) * 4;
  212. data[p + 0] = red;
  213. data[p + 1] = green;
  214. data[p + 2] = blue;
  215. //img.data[p + 3] = 255; // Set Alpha
  216. }
  217. }
  218. } else {
  219. Canvas.fillRect(img.x + x, img.y + y, w, h, color);
  220. }
  221. },
  222. putTile: function(img) {
  223. if (Canvas.prefer_js) {
  224. Canvas.rgbxImage(img.x, img.y, img.width, img.height, img.data, 0);
  225. //Canvas.ctx.putImageData(img, img.x, img.y);
  226. } else {
  227. // No-op, under gecko already done by setTile
  228. }
  229. },
  230. rgbxImage: function(x, y, width, height, arr, offset) {
  231. var img, i, j, data;
  232. //console.log("rfbxImage: img: " + img + " x: " + x + " y: " + y + " width: " + width + " height: " + height);
  233. /* Old firefox and Opera don't support createImageData */
  234. img = Canvas.ctx.getImageData(0, 0, width, height);
  235. data = img.data;
  236. for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) {
  237. data[i + 0] = arr[j + 0];
  238. data[i + 1] = arr[j + 1];
  239. data[i + 2] = arr[j + 2];
  240. data[i + 3] = 255; // Set Alpha
  241. }
  242. Canvas.ctx.putImageData(img, x, y);
  243. },
  244. cmapImage: function(x, y, width, height, arr, offset) {
  245. var img, i, j, data, rgb, cmap;
  246. img = Canvas.ctx.getImageData(0, 0, width, height);
  247. data = img.data;
  248. cmap = Canvas.colourMap;
  249. //console.log("cmapImage x: " + x + ", y: " + y + "arr.slice(0,20): " + arr.slice(0,20));
  250. for (i=0, j=offset; i < (width * height * 4); i=i+4, j += 1) {
  251. rgb = cmap[arr[j]];
  252. data[i + 0] = rgb[0];
  253. data[i + 1] = rgb[1];
  254. data[i + 2] = rgb[2];
  255. data[i + 3] = 255; // Set Alpha
  256. }
  257. Canvas.ctx.putImageData(img, x, y);
  258. },
  259. blitImage: function(x, y, width, height, arr, offset) {
  260. if (Canvas.true_color) {
  261. Canvas.rgbxImage(x, y, width, height, arr, offset);
  262. } else {
  263. Canvas.cmapImage(x, y, width, height, arr, offset);
  264. }
  265. },
  266. fillRect: function(x, y, width, height, color) {
  267. var rgb, newStyle;
  268. if (Canvas.true_color) {
  269. rgb = color;
  270. } else {
  271. rgb = Canvas.colourMap[color[0]];
  272. }
  273. if (newStyle !== Canvas.prevStyle) {
  274. newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")";
  275. Canvas.ctx.fillStyle = newStyle;
  276. Canvas.prevStyle = newStyle;
  277. }
  278. Canvas.ctx.fillRect(x, y, width, height);
  279. },
  280. copyImage: function(old_x, old_y, new_x, new_y, width, height) {
  281. Canvas.ctx.drawImage($(Canvas.id), old_x, old_y, width, height,
  282. new_x, new_y, width, height);
  283. },
  284. /* Translate DOM key event to keysym value */
  285. getKeysym: function(e) {
  286. var evt, keysym;
  287. evt = (e ? e : window.event);
  288. /* Remap modifier and special keys */
  289. switch ( evt.keyCode ) {
  290. case 8 : keysym = 0xFF08; break; // BACKSPACE
  291. case 9 : keysym = 0xFF09; break; // TAB
  292. case 13 : keysym = 0xFF0D; break; // ENTER
  293. case 27 : keysym = 0xFF1B; break; // ESCAPE
  294. case 45 : keysym = 0xFF63; break; // INSERT
  295. case 46 : keysym = 0xFFFF; break; // DELETE
  296. case 36 : keysym = 0xFF50; break; // HOME
  297. case 35 : keysym = 0xFF57; break; // END
  298. case 33 : keysym = 0xFF55; break; // PAGE_UP
  299. case 34 : keysym = 0xFF56; break; // PAGE_DOWN
  300. case 37 : keysym = 0xFF51; break; // LEFT
  301. case 38 : keysym = 0xFF52; break; // UP
  302. case 39 : keysym = 0xFF53; break; // RIGHT
  303. case 40 : keysym = 0xFF54; break; // DOWN
  304. case 112 : keysym = 0xFFBE; break; // F1
  305. case 113 : keysym = 0xFFBF; break; // F2
  306. case 114 : keysym = 0xFFC0; break; // F3
  307. case 115 : keysym = 0xFFC1; break; // F4
  308. case 116 : keysym = 0xFFC2; break; // F5
  309. case 117 : keysym = 0xFFC3; break; // F6
  310. case 118 : keysym = 0xFFC4; break; // F7
  311. case 119 : keysym = 0xFFC5; break; // F8
  312. case 120 : keysym = 0xFFC6; break; // F9
  313. case 121 : keysym = 0xFFC7; break; // F10
  314. case 122 : keysym = 0xFFC8; break; // F11
  315. case 123 : keysym = 0xFFC9; break; // F12
  316. case 16 : keysym = 0xFFE1; break; // SHIFT
  317. case 17 : keysym = 0xFFE3; break; // CONTROL
  318. //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option)
  319. case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command)
  320. default : keysym = evt.keyCode; break;
  321. }
  322. /* Remap symbols */
  323. switch (keysym) {
  324. case 186 : keysym = 59; break; // ; (IE)
  325. case 187 : keysym = 61; break; // = (IE)
  326. case 188 : keysym = 44; break; // , (Mozilla, IE)
  327. case 109 : // - (Mozilla)
  328. if (Util.Engine.gecko) {
  329. keysym = 45; }
  330. break;
  331. case 189 : keysym = 45; break; // - (IE)
  332. case 190 : keysym = 46; break; // . (Mozilla, IE)
  333. case 191 : keysym = 47; break; // / (Mozilla, IE)
  334. case 192 : keysym = 96; break; // ` (Mozilla, IE)
  335. case 219 : keysym = 91; break; // [ (Mozilla, IE)
  336. case 220 : keysym = 92; break; // \ (Mozilla, IE)
  337. case 221 : keysym = 93; break; // ] (Mozilla, IE)
  338. case 222 : keysym = 39; break; // ' (Mozilla, IE)
  339. }
  340. /* Remap shifted and unshifted keys */
  341. if (!!evt.shiftKey) {
  342. switch (keysym) {
  343. case 48 : keysym = 41 ; break; // ) (shifted 0)
  344. case 49 : keysym = 33 ; break; // ! (shifted 1)
  345. case 50 : keysym = 64 ; break; // @ (shifted 2)
  346. case 51 : keysym = 35 ; break; // # (shifted 3)
  347. case 52 : keysym = 36 ; break; // $ (shifted 4)
  348. case 53 : keysym = 37 ; break; // % (shifted 5)
  349. case 54 : keysym = 94 ; break; // ^ (shifted 6)
  350. case 55 : keysym = 38 ; break; // & (shifted 7)
  351. case 56 : keysym = 42 ; break; // * (shifted 8)
  352. case 57 : keysym = 40 ; break; // ( (shifted 9)
  353. case 59 : keysym = 58 ; break; // : (shifted `)
  354. case 61 : keysym = 43 ; break; // + (shifted ;)
  355. case 44 : keysym = 60 ; break; // < (shifted ,)
  356. case 45 : keysym = 95 ; break; // _ (shifted -)
  357. case 46 : keysym = 62 ; break; // > (shifted .)
  358. case 47 : keysym = 63 ; break; // ? (shifted /)
  359. case 96 : keysym = 126; break; // ~ (shifted `)
  360. case 91 : keysym = 123; break; // { (shifted [)
  361. case 92 : keysym = 124; break; // | (shifted \)
  362. case 93 : keysym = 125; break; // } (shifted ])
  363. case 39 : keysym = 34 ; break; // " (shifted ')
  364. }
  365. } else if ((keysym >= 65) && (keysym <=90)) {
  366. /* Remap unshifted A-Z */
  367. keysym += 32;
  368. }
  369. return keysym;
  370. }
  371. };