canvas.js 26 KB

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