novnc-rails 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/COPYING +0 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +0 -0
  5. data/lib/novnc-rails.rb +8 -0
  6. data/lib/novnc-rails/version.rb +5 -0
  7. data/vendor/assets/javascripts/noVNC/Orbitron700.ttf +0 -0
  8. data/vendor/assets/javascripts/noVNC/Orbitron700.woff +0 -0
  9. data/vendor/assets/javascripts/noVNC/base.css +512 -0
  10. data/vendor/assets/javascripts/noVNC/base64.js +113 -0
  11. data/vendor/assets/javascripts/noVNC/black.css +71 -0
  12. data/vendor/assets/javascripts/noVNC/blue.css +64 -0
  13. data/vendor/assets/javascripts/noVNC/des.js +276 -0
  14. data/vendor/assets/javascripts/noVNC/display.js +751 -0
  15. data/vendor/assets/javascripts/noVNC/input.js +388 -0
  16. data/vendor/assets/javascripts/noVNC/jsunzip.js +676 -0
  17. data/vendor/assets/javascripts/noVNC/keyboard.js +543 -0
  18. data/vendor/assets/javascripts/noVNC/keysym.js +378 -0
  19. data/vendor/assets/javascripts/noVNC/keysymdef.js +15 -0
  20. data/vendor/assets/javascripts/noVNC/logo.js +1 -0
  21. data/vendor/assets/javascripts/noVNC/playback.js +102 -0
  22. data/vendor/assets/javascripts/noVNC/rfb.js +1889 -0
  23. data/vendor/assets/javascripts/noVNC/ui.js +979 -0
  24. data/vendor/assets/javascripts/noVNC/util.js +656 -0
  25. data/vendor/assets/javascripts/noVNC/web-socket-js/README.txt +109 -0
  26. data/vendor/assets/javascripts/noVNC/web-socket-js/WebSocketMain.swf +0 -0
  27. data/vendor/assets/javascripts/noVNC/web-socket-js/swfobject.js +4 -0
  28. data/vendor/assets/javascripts/noVNC/web-socket-js/web_socket.js +391 -0
  29. data/vendor/assets/javascripts/noVNC/websock.js +388 -0
  30. data/vendor/assets/javascripts/noVNC/webutil.js +239 -0
  31. metadata +86 -0
@@ -0,0 +1,751 @@
1
+ /*
2
+ * noVNC: HTML5 VNC client
3
+ * Copyright (C) 2012 Joel Martin
4
+ * Licensed under MPL 2.0 (see LICENSE.txt)
5
+ *
6
+ * See README.md for usage and integration instructions.
7
+ */
8
+
9
+ /*jslint browser: true, white: false */
10
+ /*global Util, Base64, changeCursor */
11
+
12
+ var Display;
13
+
14
+ (function () {
15
+ "use strict";
16
+
17
+ Display = function (defaults) {
18
+ this._drawCtx = null;
19
+ this._c_forceCanvas = false;
20
+
21
+ this._renderQ = []; // queue drawing actions for in-oder rendering
22
+
23
+ // the full frame buffer (logical canvas) size
24
+ this._fb_width = 0;
25
+ this._fb_height = 0;
26
+
27
+ // the visible "physical canvas" viewport
28
+ this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 };
29
+ this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 };
30
+
31
+ this._prevDrawStyle = "";
32
+ this._tile = null;
33
+ this._tile16x16 = null;
34
+ this._tile_x = 0;
35
+ this._tile_y = 0;
36
+
37
+ Util.set_defaults(this, defaults, {
38
+ 'true_color': true,
39
+ 'colourMap': [],
40
+ 'scale': 1.0,
41
+ 'viewport': false,
42
+ 'render_mode': ''
43
+ });
44
+
45
+ Util.Debug(">> Display.constructor");
46
+
47
+ if (!this._target) {
48
+ throw new Error("Target must be set");
49
+ }
50
+
51
+ if (typeof this._target === 'string') {
52
+ throw new Error('target must be a DOM element');
53
+ }
54
+
55
+ if (!this._target.getContext) {
56
+ throw new Error("no getContext method");
57
+ }
58
+
59
+ if (!this._drawCtx) {
60
+ this._drawCtx = this._target.getContext('2d');
61
+ }
62
+
63
+ Util.Debug("User Agent: " + navigator.userAgent);
64
+ if (Util.Engine.gecko) { Util.Debug("Browser: gecko " + Util.Engine.gecko); }
65
+ if (Util.Engine.webkit) { Util.Debug("Browser: webkit " + Util.Engine.webkit); }
66
+ if (Util.Engine.trident) { Util.Debug("Browser: trident " + Util.Engine.trident); }
67
+ if (Util.Engine.presto) { Util.Debug("Browser: presto " + Util.Engine.presto); }
68
+
69
+ this.clear();
70
+
71
+ // Check canvas features
72
+ if ('createImageData' in this._drawCtx) {
73
+ this._render_mode = 'canvas rendering';
74
+ } else {
75
+ throw new Error("Canvas does not support createImageData");
76
+ }
77
+
78
+ if (this._prefer_js === null) {
79
+ Util.Info("Prefering javascript operations");
80
+ this._prefer_js = true;
81
+ }
82
+
83
+ // Determine browser support for setting the cursor via data URI scheme
84
+ var curDat = [];
85
+ for (var i = 0; i < 8 * 8 * 4; i++) {
86
+ curDat.push(255);
87
+ }
88
+ try {
89
+ var curSave = this._target.style.cursor;
90
+ Display.changeCursor(this._target, curDat, curDat, 2, 2, 8, 8);
91
+ if (this._target.style.cursor) {
92
+ if (this._cursor_uri === null || this._cursor_uri === undefined) {
93
+ this._cursor_uri = true;
94
+ }
95
+ Util.Info("Data URI scheme cursor supported");
96
+ this._target.style.cursor = curSave;
97
+ } else {
98
+ if (this._cursor_uri === null || this._cursor_uri === undefined) {
99
+ this._cursor_uri = false;
100
+ }
101
+ Util.Warn("Data URI scheme cursor not supported");
102
+ this._target.style.cursor = "none";
103
+ }
104
+ } catch (exc) {
105
+ Util.Error("Data URI scheme cursor test exception: " + exc);
106
+ this._cursor_uri = false;
107
+ }
108
+
109
+ Util.Debug("<< Display.constructor");
110
+ };
111
+
112
+ Display.prototype = {
113
+ // Public methods
114
+ viewportChange: function (deltaX, deltaY, width, height) {
115
+ var vp = this._viewportLoc;
116
+ var cr = this._cleanRect;
117
+ var canvas = this._target;
118
+
119
+ if (!this._viewport) {
120
+ Util.Debug("Setting viewport to full display region");
121
+ deltaX = -vp.w; // clamped later of out of bounds
122
+ deltaY = -vp.h;
123
+ width = this._fb_width;
124
+ height = this._fb_height;
125
+ }
126
+
127
+ if (typeof(deltaX) === "undefined") { deltaX = 0; }
128
+ if (typeof(deltaY) === "undefined") { deltaY = 0; }
129
+ if (typeof(width) === "undefined") { width = vp.w; }
130
+ if (typeof(height) === "undefined") { height = vp.h; }
131
+
132
+ // Size change
133
+ if (width > this._fb_width) { width = this._fb_width; }
134
+ if (height > this._fb_height) { height = this._fb_height; }
135
+
136
+ if (vp.w !== width || vp.h !== height) {
137
+ // Change width
138
+ if (width < vp.w && cr.x2 > vp.x + width - 1) {
139
+ cr.x2 = vp.x + width - 1;
140
+ }
141
+ vp.w = width;
142
+
143
+ // Change height
144
+ if (height < vp.h && cr.y2 > vp.y + height - 1) {
145
+ cr.y2 = vp.y + height - 1;
146
+ }
147
+ vp.h = height;
148
+
149
+ var saveImg = null;
150
+ if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
151
+ var img_width = canvas.width < vp.w ? canvas.width : vp.w;
152
+ var img_height = canvas.height < vp.h ? canvas.height : vp.h;
153
+ saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
154
+ }
155
+
156
+ canvas.width = vp.w;
157
+ canvas.height = vp.h;
158
+
159
+ if (saveImg) {
160
+ this._drawCtx.putImageData(saveImg, 0, 0);
161
+ }
162
+ }
163
+
164
+ var vx2 = vp.x + vp.w - 1;
165
+ var vy2 = vp.y + vp.h - 1;
166
+
167
+ // Position change
168
+
169
+ if (deltaX < 0 && vp.x + deltaX < 0) {
170
+ deltaX = -vp.x;
171
+ }
172
+ if (vx2 + deltaX >= this._fb_width) {
173
+ deltaX -= vx2 + deltaX - this._fb_width + 1;
174
+ }
175
+
176
+ if (vp.y + deltaY < 0) {
177
+ deltaY = -vp.y;
178
+ }
179
+ if (vy2 + deltaY >= this._fb_height) {
180
+ deltaY -= (vy2 + deltaY - this._fb_height + 1);
181
+ }
182
+
183
+ if (deltaX === 0 && deltaY === 0) {
184
+ return;
185
+ }
186
+ Util.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
187
+
188
+ vp.x += deltaX;
189
+ vx2 += deltaX;
190
+ vp.y += deltaY;
191
+ vy2 += deltaY;
192
+
193
+ // Update the clean rectangle
194
+ if (vp.x > cr.x1) {
195
+ cr.x1 = vp.x;
196
+ }
197
+ if (vx2 < cr.x2) {
198
+ cr.x2 = vx2;
199
+ }
200
+ if (vp.y > cr.y1) {
201
+ cr.y1 = vp.y;
202
+ }
203
+ if (vy2 < cr.y2) {
204
+ cr.y2 = vy2;
205
+ }
206
+
207
+ var x1, w;
208
+ if (deltaX < 0) {
209
+ // Shift viewport left, redraw left section
210
+ x1 = 0;
211
+ w = -deltaX;
212
+ } else {
213
+ // Shift viewport right, redraw right section
214
+ x1 = vp.w - deltaX;
215
+ w = deltaX;
216
+ }
217
+
218
+ var y1, h;
219
+ if (deltaY < 0) {
220
+ // Shift viewport up, redraw top section
221
+ y1 = 0;
222
+ h = -deltaY;
223
+ } else {
224
+ // Shift viewport down, redraw bottom section
225
+ y1 = vp.h - deltaY;
226
+ h = deltaY;
227
+ }
228
+
229
+ // Copy the valid part of the viewport to the shifted location
230
+ var saveStyle = this._drawCtx.fillStyle;
231
+ this._drawCtx.fillStyle = "rgb(255,255,255)";
232
+ if (deltaX !== 0) {
233
+ this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, -deltaX, 0, vp.w, vp.h);
234
+ this._drawCtx.fillRect(x1, 0, w, vp.h);
235
+ }
236
+ if (deltaY !== 0) {
237
+ this._drawCtx.drawImage(canvas, 0, 0, vp.w, vp.h, 0, -deltaY, vp.w, vp.h);
238
+ this._drawCtx.fillRect(0, y1, vp.w, h);
239
+ }
240
+ this._drawCtx.fillStyle = saveStyle;
241
+ },
242
+
243
+ // Return a map of clean and dirty areas of the viewport and reset the
244
+ // tracking of clean and dirty areas
245
+ //
246
+ // Returns: { 'cleanBox': { 'x': x, 'y': y, 'w': w, 'h': h},
247
+ // 'dirtyBoxes': [{ 'x': x, 'y': y, 'w': w, 'h': h }, ...] }
248
+ getCleanDirtyReset: function () {
249
+ var vp = this._viewportLoc;
250
+ var cr = this._cleanRect;
251
+
252
+ var cleanBox = { 'x': cr.x1, 'y': cr.y1,
253
+ 'w': cr.x2 - cr.x1 + 1, 'h': cr.y2 - cr.y1 + 1 };
254
+
255
+ var dirtyBoxes = [];
256
+ if (cr.x1 >= cr.x2 || cr.y1 >= cr.y2) {
257
+ // Whole viewport is dirty
258
+ dirtyBoxes.push({ 'x': vp.x, 'y': vp.y, 'w': vp.w, 'h': vp.h });
259
+ } else {
260
+ // Redraw dirty regions
261
+ var vx2 = vp.x + vp.w - 1;
262
+ var vy2 = vp.y + vp.h - 1;
263
+
264
+ if (vp.x < cr.x1) {
265
+ // left side dirty region
266
+ dirtyBoxes.push({'x': vp.x, 'y': vp.y,
267
+ 'w': cr.x1 - vp.x + 1, 'h': vp.h});
268
+ }
269
+ if (vx2 > cr.x2) {
270
+ // right side dirty region
271
+ dirtyBoxes.push({'x': cr.x2 + 1, 'y': vp.y,
272
+ 'w': vx2 - cr.x2, 'h': vp.h});
273
+ }
274
+ if(vp.y < cr.y1) {
275
+ // top/middle dirty region
276
+ dirtyBoxes.push({'x': cr.x1, 'y': vp.y,
277
+ 'w': cr.x2 - cr.x1 + 1, 'h': cr.y1 - vp.y});
278
+ }
279
+ if (vy2 > cr.y2) {
280
+ // bottom/middle dirty region
281
+ dirtyBoxes.push({'x': cr.x1, 'y': cr.y2 + 1,
282
+ 'w': cr.x2 - cr.x1 + 1, 'h': vy2 - cr.y2});
283
+ }
284
+ }
285
+
286
+ this._cleanRect = {'x1': vp.x, 'y1': vp.y,
287
+ 'x2': vp.x + vp.w - 1, 'y2': vp.y + vp.h - 1};
288
+
289
+ return {'cleanBox': cleanBox, 'dirtyBoxes': dirtyBoxes};
290
+ },
291
+
292
+ absX: function (x) {
293
+ return x + this._viewportLoc.x;
294
+ },
295
+
296
+ absY: function (y) {
297
+ return y + this._viewportLoc.y;
298
+ },
299
+
300
+ resize: function (width, height) {
301
+ this._prevDrawStyle = "";
302
+
303
+ this._fb_width = width;
304
+ this._fb_height = height;
305
+
306
+ this._rescale(this._scale);
307
+
308
+ this.viewportChange();
309
+ },
310
+
311
+ clear: function () {
312
+ if (this._logo) {
313
+ this.resize(this._logo.width, this._logo.height);
314
+ this.blitStringImage(this._logo.data, 0, 0);
315
+ } else {
316
+ if (Util.Engine.trident === 6) {
317
+ // NB(directxman12): there's a bug in IE10 where we can fail to actually
318
+ // clear the canvas here because of the resize.
319
+ // Clearing the current viewport first fixes the issue
320
+ this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h);
321
+ }
322
+ this.resize(240, 20);
323
+ this._drawCtx.clearRect(0, 0, this._viewportLoc.w, this._viewportLoc.h);
324
+ }
325
+
326
+ this._renderQ = [];
327
+ },
328
+
329
+ fillRect: function (x, y, width, height, color) {
330
+ this._setFillColor(color);
331
+ this._drawCtx.fillRect(x - this._viewportLoc.x, y - this._viewportLoc.y, width, height);
332
+ },
333
+
334
+ copyImage: function (old_x, old_y, new_x, new_y, w, h) {
335
+ var x1 = old_x - this._viewportLoc.x;
336
+ var y1 = old_y - this._viewportLoc.y;
337
+ var x2 = new_x - this._viewportLoc.x;
338
+ var y2 = new_y - this._viewportLoc.y;
339
+
340
+ this._drawCtx.drawImage(this._target, x1, y1, w, h, x2, y2, w, h);
341
+ },
342
+
343
+ // start updating a tile
344
+ startTile: function (x, y, width, height, color) {
345
+ this._tile_x = x;
346
+ this._tile_y = y;
347
+ if (width === 16 && height === 16) {
348
+ this._tile = this._tile16x16;
349
+ } else {
350
+ this._tile = this._drawCtx.createImageData(width, height);
351
+ }
352
+
353
+ if (this._prefer_js) {
354
+ var bgr;
355
+ if (this._true_color) {
356
+ bgr = color;
357
+ } else {
358
+ bgr = this._colourMap[color[0]];
359
+ }
360
+ var red = bgr[2];
361
+ var green = bgr[1];
362
+ var blue = bgr[0];
363
+
364
+ var data = this._tile.data;
365
+ for (var i = 0; i < width * height * 4; i += 4) {
366
+ data[i] = red;
367
+ data[i + 1] = green;
368
+ data[i + 2] = blue;
369
+ data[i + 3] = 255;
370
+ }
371
+ } else {
372
+ this.fillRect(x, y, width, height, color);
373
+ }
374
+ },
375
+
376
+ // update sub-rectangle of the current tile
377
+ subTile: function (x, y, w, h, color) {
378
+ if (this._prefer_js) {
379
+ var bgr;
380
+ if (this._true_color) {
381
+ bgr = color;
382
+ } else {
383
+ bgr = this._colourMap[color[0]];
384
+ }
385
+ var red = bgr[2];
386
+ var green = bgr[1];
387
+ var blue = bgr[0];
388
+ var xend = x + w;
389
+ var yend = y + h;
390
+
391
+ var data = this._tile.data;
392
+ var width = this._tile.width;
393
+ for (var j = y; j < yend; j++) {
394
+ for (var i = x; i < xend; i++) {
395
+ var p = (i + (j * width)) * 4;
396
+ data[p] = red;
397
+ data[p + 1] = green;
398
+ data[p + 2] = blue;
399
+ data[p + 3] = 255;
400
+ }
401
+ }
402
+ } else {
403
+ this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color);
404
+ }
405
+ },
406
+
407
+ // draw the current tile to the screen
408
+ finishTile: function () {
409
+ if (this._prefer_js) {
410
+ this._drawCtx.putImageData(this._tile, this._tile_x - this._viewportLoc.x,
411
+ this._tile_y - this._viewportLoc.y);
412
+ }
413
+ // else: No-op -- already done by setSubTile
414
+ },
415
+
416
+ blitImage: function (x, y, width, height, arr, offset) {
417
+ if (this._true_color) {
418
+ this._bgrxImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
419
+ } else {
420
+ this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
421
+ }
422
+ },
423
+
424
+ blitRgbImage: function (x, y , width, height, arr, offset) {
425
+ if (this._true_color) {
426
+ this._rgbImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
427
+ } else {
428
+ // probably wrong?
429
+ this._cmapImageData(x, y, this._viewportLoc.x, this._viewportLoc.y, width, height, arr, offset);
430
+ }
431
+ },
432
+
433
+ blitStringImage: function (str, x, y) {
434
+ var img = new Image();
435
+ img.onload = function () {
436
+ this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y);
437
+ }.bind(this);
438
+ img.src = str;
439
+ return img; // for debugging purposes
440
+ },
441
+
442
+ // wrap ctx.drawImage but relative to viewport
443
+ drawImage: function (img, x, y) {
444
+ this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y);
445
+ },
446
+
447
+ renderQ_push: function (action) {
448
+ this._renderQ.push(action);
449
+ if (this._renderQ.length === 1) {
450
+ // If this can be rendered immediately it will be, otherwise
451
+ // the scanner will start polling the queue (every
452
+ // requestAnimationFrame interval)
453
+ this._scan_renderQ();
454
+ }
455
+ },
456
+
457
+ changeCursor: function (pixels, mask, hotx, hoty, w, h) {
458
+ if (this._cursor_uri === false) {
459
+ Util.Warn("changeCursor called but no cursor data URI support");
460
+ return;
461
+ }
462
+
463
+ if (this._true_color) {
464
+ Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
465
+ } else {
466
+ Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h, this._colourMap);
467
+ }
468
+ },
469
+
470
+ defaultCursor: function () {
471
+ this._target.style.cursor = "default";
472
+ },
473
+
474
+ disableLocalCursor: function () {
475
+ this._target.style.cursor = "none";
476
+ },
477
+
478
+ // Overridden getters/setters
479
+ get_context: function () {
480
+ return this._drawCtx;
481
+ },
482
+
483
+ set_scale: function (scale) {
484
+ this._rescale(scale);
485
+ },
486
+
487
+ set_width: function (w) {
488
+ this.resize(w, this._fb_height);
489
+ },
490
+ get_width: function () {
491
+ return this._fb_width;
492
+ },
493
+
494
+ set_height: function (h) {
495
+ this.resize(this._fb_width, h);
496
+ },
497
+ get_height: function () {
498
+ return this._fb_height;
499
+ },
500
+
501
+ // Private Methods
502
+ _rescale: function (factor) {
503
+ var canvas = this._target;
504
+ var properties = ['transform', 'WebkitTransform', 'MozTransform'];
505
+ var transform_prop;
506
+ while ((transform_prop = properties.shift())) {
507
+ if (typeof canvas.style[transform_prop] !== 'undefined') {
508
+ break;
509
+ }
510
+ }
511
+
512
+ if (transform_prop === null) {
513
+ Util.Debug("No scaling support");
514
+ return;
515
+ }
516
+
517
+ if (typeof(factor) === "undefined") {
518
+ factor = this._scale;
519
+ } else if (factor > 1.0) {
520
+ factor = 1.0;
521
+ } else if (factor < 0.1) {
522
+ factor = 0.1;
523
+ }
524
+
525
+ if (this._scale === factor) {
526
+ return;
527
+ }
528
+
529
+ this._scale = factor;
530
+ var x = canvas.width - (canvas.width * factor);
531
+ var y = canvas.height - (canvas.height * factor);
532
+ canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)';
533
+ },
534
+
535
+ _setFillColor: function (color) {
536
+ var bgr;
537
+ if (this._true_color) {
538
+ bgr = color;
539
+ } else {
540
+ bgr = this._colourMap[color[0]];
541
+ }
542
+
543
+ var newStyle = 'rgb(' + bgr[2] + ',' + bgr[1] + ',' + bgr[0] + ')';
544
+ if (newStyle !== this._prevDrawStyle) {
545
+ this._drawCtx.fillStyle = newStyle;
546
+ this._prevDrawStyle = newStyle;
547
+ }
548
+ },
549
+
550
+ _rgbImageData: function (x, y, vx, vy, width, height, arr, offset) {
551
+ var img = this._drawCtx.createImageData(width, height);
552
+ var data = img.data;
553
+ for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
554
+ data[i] = arr[j];
555
+ data[i + 1] = arr[j + 1];
556
+ data[i + 2] = arr[j + 2];
557
+ data[i + 3] = 255; // Alpha
558
+ }
559
+ this._drawCtx.putImageData(img, x - vx, y - vy);
560
+ },
561
+
562
+ _bgrxImageData: function (x, y, vx, vy, width, height, arr, offset) {
563
+ var img = this._drawCtx.createImageData(width, height);
564
+ var data = img.data;
565
+ for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
566
+ data[i] = arr[j + 2];
567
+ data[i + 1] = arr[j + 1];
568
+ data[i + 2] = arr[j];
569
+ data[i + 3] = 255; // Alpha
570
+ }
571
+ this._drawCtx.putImageData(img, x - vx, y - vy);
572
+ },
573
+
574
+ _cmapImageData: function (x, y, vx, vy, width, height, arr, offset) {
575
+ var img = this._drawCtx.createImageData(width, height);
576
+ var data = img.data;
577
+ var cmap = this._colourMap;
578
+ for (var i = 0, j = offset; i < width * height * 4; i += 4, j++) {
579
+ var bgr = cmap[arr[j]];
580
+ data[i] = bgr[2];
581
+ data[i + 1] = bgr[1];
582
+ data[i + 2] = bgr[0];
583
+ data[i + 3] = 255; // Alpha
584
+ }
585
+ this._drawCtx.putImageData(img, x - vx, y - vy);
586
+ },
587
+
588
+ _scan_renderQ: function () {
589
+ var ready = true;
590
+ while (ready && this._renderQ.length > 0) {
591
+ var a = this._renderQ[0];
592
+ switch (a.type) {
593
+ case 'copy':
594
+ this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height);
595
+ break;
596
+ case 'fill':
597
+ this.fillRect(a.x, a.y, a.width, a.height, a.color);
598
+ break;
599
+ case 'blit':
600
+ this.blitImage(a.x, a.y, a.width, a.height, a.data, 0);
601
+ break;
602
+ case 'blitRgb':
603
+ this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0);
604
+ break;
605
+ case 'img':
606
+ if (a.img.complete) {
607
+ this.drawImage(a.img, a.x, a.y);
608
+ } else {
609
+ // We need to wait for this image to 'load'
610
+ // to keep things in-order
611
+ ready = false;
612
+ }
613
+ break;
614
+ }
615
+
616
+ if (ready) {
617
+ this._renderQ.shift();
618
+ }
619
+ }
620
+
621
+ if (this._renderQ.length > 0) {
622
+ requestAnimFrame(this._scan_renderQ.bind(this));
623
+ }
624
+ },
625
+ };
626
+
627
+ Util.make_properties(Display, [
628
+ ['target', 'wo', 'dom'], // Canvas element for rendering
629
+ ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only)
630
+ ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "data": data}
631
+ ['true_color', 'rw', 'bool'], // Use true-color pixel data
632
+ ['colourMap', 'rw', 'arr'], // Colour map array (when not true-color)
633
+ ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0
634
+ ['viewport', 'rw', 'bool'], // Use a viewport set with viewportChange()
635
+ ['width', 'rw', 'int'], // Display area width
636
+ ['height', 'rw', 'int'], // Display area height
637
+
638
+ ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
639
+
640
+ ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods
641
+ ['cursor_uri', 'rw', 'raw'] // Can we render cursor using data URI
642
+ ]);
643
+
644
+ // Class Methods
645
+ Display.changeCursor = function (target, pixels, mask, hotx, hoty, w0, h0, cmap) {
646
+ var w = w0;
647
+ var h = h0;
648
+ if (h < w) {
649
+ h = w; // increase h to make it square
650
+ } else {
651
+ w = h; // increase w to make it square
652
+ }
653
+
654
+ var cur = [];
655
+
656
+ // Push multi-byte little-endian values
657
+ cur.push16le = function (num) {
658
+ this.push(num & 0xFF, (num >> 8) & 0xFF);
659
+ };
660
+ cur.push32le = function (num) {
661
+ this.push(num & 0xFF,
662
+ (num >> 8) & 0xFF,
663
+ (num >> 16) & 0xFF,
664
+ (num >> 24) & 0xFF);
665
+ };
666
+
667
+ var IHDRsz = 40;
668
+ var RGBsz = w * h * 4;
669
+ var XORsz = Math.ceil((w * h) / 8.0);
670
+ var ANDsz = Math.ceil((w * h) / 8.0);
671
+
672
+ cur.push16le(0); // 0: Reserved
673
+ cur.push16le(2); // 2: .CUR type
674
+ cur.push16le(1); // 4: Number of images, 1 for non-animated ico
675
+
676
+ // Cursor #1 header (ICONDIRENTRY)
677
+ cur.push(w); // 6: width
678
+ cur.push(h); // 7: height
679
+ cur.push(0); // 8: colors, 0 -> true-color
680
+ cur.push(0); // 9: reserved
681
+ cur.push16le(hotx); // 10: hotspot x coordinate
682
+ cur.push16le(hoty); // 12: hotspot y coordinate
683
+ cur.push32le(IHDRsz + RGBsz + XORsz + ANDsz);
684
+ // 14: cursor data byte size
685
+ cur.push32le(22); // 18: offset of cursor data in the file
686
+
687
+ // Cursor #1 InfoHeader (ICONIMAGE/BITMAPINFO)
688
+ cur.push32le(IHDRsz); // 22: InfoHeader size
689
+ cur.push32le(w); // 26: Cursor width
690
+ cur.push32le(h * 2); // 30: XOR+AND height
691
+ cur.push16le(1); // 34: number of planes
692
+ cur.push16le(32); // 36: bits per pixel
693
+ cur.push32le(0); // 38: Type of compression
694
+
695
+ cur.push32le(XORsz + ANDsz);
696
+ // 42: Size of Image
697
+ cur.push32le(0); // 46: reserved
698
+ cur.push32le(0); // 50: reserved
699
+ cur.push32le(0); // 54: reserved
700
+ cur.push32le(0); // 58: reserved
701
+
702
+ // 62: color data (RGBQUAD icColors[])
703
+ var y, x;
704
+ for (y = h - 1; y >= 0; y--) {
705
+ for (x = 0; x < w; x++) {
706
+ if (x >= w0 || y >= h0) {
707
+ cur.push(0); // blue
708
+ cur.push(0); // green
709
+ cur.push(0); // red
710
+ cur.push(0); // alpha
711
+ } else {
712
+ var idx = y * Math.ceil(w0 / 8) + Math.floor(x / 8);
713
+ var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
714
+ if (cmap) {
715
+ idx = (w0 * y) + x;
716
+ var rgb = cmap[pixels[idx]];
717
+ cur.push(rgb[2]); // blue
718
+ cur.push(rgb[1]); // green
719
+ cur.push(rgb[0]); // red
720
+ cur.push(alpha); // alpha
721
+ } else {
722
+ idx = ((w0 * y) + x) * 4;
723
+ cur.push(pixels[idx + 2]); // blue
724
+ cur.push(pixels[idx + 1]); // green
725
+ cur.push(pixels[idx]); // red
726
+ cur.push(alpha); // alpha
727
+ }
728
+ }
729
+ }
730
+ }
731
+
732
+ // XOR/bitmask data (BYTE icXOR[])
733
+ // (ignored, just needs to be the right size)
734
+ for (y = 0; y < h; y++) {
735
+ for (x = 0; x < Math.ceil(w / 8); x++) {
736
+ cur.push(0);
737
+ }
738
+ }
739
+
740
+ // AND/bitmask data (BYTE icAND[])
741
+ // (ignored, just needs to be the right size)
742
+ for (y = 0; y < h; y++) {
743
+ for (x = 0; x < Math.ceil(w / 8); x++) {
744
+ cur.push(0);
745
+ }
746
+ }
747
+
748
+ var url = 'data:image/x-icon;base64,' + Base64.encode(cur);
749
+ target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
750
+ };
751
+ })();