]> git.proxmox.com Git - mirror_novnc.git/blame - core/display.js
Change some attributes to arguments
[mirror_novnc.git] / core / display.js
CommitLineData
c4164bda
JM
1/*
2 * noVNC: HTML5 VNC client
d58f8b51 3 * Copyright (C) 2012 Joel Martin
fdedbafb 4 * Copyright (C) 2015 Samuel Mannehed for Cendio AB
1d728ace 5 * Licensed under MPL 2.0 (see LICENSE.txt)
c4164bda
JM
6 *
7 * See README.md for usage and integration instructions.
8 */
c4164bda 9
1e13775b 10/*jslint browser: true, white: false */
d3796c14 11/*global Util, Base64, changeCursor */
c4164bda 12
fb7e99e8 13import { browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js';
6d6f0db0
SR
14import { set_defaults, make_properties } from './util/properties.js';
15import * as Log from './util/logging.js';
3ae0bb09
SR
16import Base64 from "./base64.js";
17
3d7bb020 18export default function Display(target, defaults) {
ae510306
SR
19 this._drawCtx = null;
20 this._c_forceCanvas = false;
21
22 this._renderQ = []; // queue drawing actions for in-oder rendering
d9ca5e5b 23 this._flushing = false;
ae510306
SR
24
25 // the full frame buffer (logical canvas) size
26 this._fb_width = 0;
27 this._fb_height = 0;
28
ae510306
SR
29 this._prevDrawStyle = "";
30 this._tile = null;
31 this._tile16x16 = null;
32 this._tile_x = 0;
33 this._tile_y = 0;
34
6d6f0db0 35 set_defaults(this, defaults, {
ae510306
SR
36 'scale': 1.0,
37 'viewport': false,
d9ca5e5b
PO
38 'render_mode': '',
39 "onFlush": function () {},
ae510306
SR
40 });
41
6d6f0db0 42 Log.Debug(">> Display.constructor");
ae510306 43
2ba767a7 44 // The visible canvas
3d7bb020
PO
45 this._target = target;
46
ae510306
SR
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
2ba767a7
PO
59 this._targetCtx = this._target.getContext('2d');
60
adf345fd
PO
61 // the visible canvas viewport (i.e. what actually gets seen)
62 this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
63
2ba767a7
PO
64 // The hidden canvas, where we do the actual rendering
65 this._backbuffer = document.createElement('canvas');
66 this._drawCtx = this._backbuffer.getContext('2d');
ae510306 67
84cd0e71
PO
68 this._damageBounds = { left:0, top:0,
69 right: this._backbuffer.width,
70 bottom: this._backbuffer.height };
71
6d6f0db0 72 Log.Debug("User Agent: " + navigator.userAgent);
ae510306
SR
73
74 this.clear();
75
76 // Check canvas features
77 if ('createImageData' in this._drawCtx) {
78 this._render_mode = 'canvas rendering';
79 } else {
80 throw new Error("Canvas does not support createImageData");
81 }
82
83 if (this._prefer_js === null) {
6d6f0db0 84 Log.Info("Prefering javascript operations");
ae510306
SR
85 this._prefer_js = true;
86 }
87
88 // Determine browser support for setting the cursor via data URI scheme
89 if (this._cursor_uri || this._cursor_uri === null ||
90 this._cursor_uri === undefined) {
6d6f0db0 91 this._cursor_uri = cursorURIsSupported();
ae510306
SR
92 }
93
6d6f0db0 94 Log.Debug("<< Display.constructor");
ae510306 95};
ff36b127 96
6d6f0db0
SR
97var SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
98try {
99 new ImageData(new Uint8ClampedArray(4), 1, 1);
100 SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
101} catch (ex) {
102 // ignore failure
103}
104
105Display.prototype = {
106 // Public methods
107 viewportChangePos: function (deltaX, deltaY) {
108 var vp = this._viewportLoc;
109 deltaX = Math.floor(deltaX);
110 deltaY = Math.floor(deltaY);
111
112 if (!this._viewport) {
113 deltaX = -vp.w; // clamped later of out of bounds
114 deltaY = -vp.h;
115 }
d1800d09 116
6d6f0db0
SR
117 var vx2 = vp.x + vp.w - 1;
118 var vy2 = vp.y + vp.h - 1;
490d471c 119
6d6f0db0 120 // Position change
1e13775b 121
6d6f0db0
SR
122 if (deltaX < 0 && vp.x + deltaX < 0) {
123 deltaX = -vp.x;
124 }
125 if (vx2 + deltaX >= this._fb_width) {
126 deltaX -= vx2 + deltaX - this._fb_width + 1;
127 }
54e7cbdf 128
6d6f0db0
SR
129 if (vp.y + deltaY < 0) {
130 deltaY = -vp.y;
131 }
132 if (vy2 + deltaY >= this._fb_height) {
133 deltaY -= (vy2 + deltaY - this._fb_height + 1);
134 }
54e7cbdf 135
6d6f0db0
SR
136 if (deltaX === 0 && deltaY === 0) {
137 return;
138 }
139 Log.Debug("viewportChange deltaX: " + deltaX + ", deltaY: " + deltaY);
54e7cbdf 140
6d6f0db0
SR
141 vp.x += deltaX;
142 vp.y += deltaY;
54e7cbdf 143
6d6f0db0 144 this._damage(vp.x, vp.y, vp.w, vp.h);
54e7cbdf 145
6d6f0db0
SR
146 this.flip();
147 },
1e13775b 148
6d6f0db0 149 viewportChangeSize: function(width, height) {
a8255821 150
6d6f0db0
SR
151 if (!this._viewport ||
152 typeof(width) === "undefined" ||
153 typeof(height) === "undefined") {
84cd0e71 154
6d6f0db0
SR
155 Log.Debug("Setting viewport to full display region");
156 width = this._fb_width;
157 height = this._fb_height;
158 }
1e13775b 159
6d6f0db0
SR
160 if (width > this._fb_width) {
161 width = this._fb_width;
162 }
163 if (height > this._fb_height) {
164 height = this._fb_height;
165 }
636be753 166
6d6f0db0
SR
167 var vp = this._viewportLoc;
168 if (vp.w !== width || vp.h !== height) {
169 vp.w = width;
170 vp.h = height;
636be753 171
6d6f0db0
SR
172 var canvas = this._target;
173 canvas.width = width;
174 canvas.height = height;
636be753 175
6d6f0db0
SR
176 // The position might need to be updated if we've grown
177 this.viewportChangePos(0, 0);
adf345fd 178
6d6f0db0
SR
179 this._damage(vp.x, vp.y, vp.w, vp.h);
180 this.flip();
636be753 181
6d6f0db0
SR
182 // Update the visible size of the target canvas
183 this._rescale(this._scale);
184 }
185 },
adf345fd 186
6d6f0db0
SR
187 absX: function (x) {
188 return x / this._scale + this._viewportLoc.x;
189 },
adf345fd 190
6d6f0db0
SR
191 absY: function (y) {
192 return y / this._scale + this._viewportLoc.y;
193 },
adf345fd 194
6d6f0db0
SR
195 resize: function (width, height) {
196 this._prevDrawStyle = "";
636be753 197
6d6f0db0
SR
198 this._fb_width = width;
199 this._fb_height = height;
1e13775b 200
6d6f0db0
SR
201 var canvas = this._backbuffer;
202 if (canvas.width !== width || canvas.height !== height) {
1e13775b 203
6d6f0db0
SR
204 // We have to save the canvas data since changing the size will clear it
205 var saveImg = null;
206 if (canvas.width > 0 && canvas.height > 0) {
207 saveImg = this._drawCtx.getImageData(0, 0, canvas.width, canvas.height);
208 }
1e13775b 209
6d6f0db0
SR
210 if (canvas.width !== width) {
211 canvas.width = width;
212 }
213 if (canvas.height !== height) {
214 canvas.height = height;
215 }
1e13775b 216
6d6f0db0
SR
217 if (saveImg) {
218 this._drawCtx.putImageData(saveImg, 0, 0);
219 }
220 }
2ba767a7 221
6d6f0db0
SR
222 // Readjust the viewport as it may be incorrectly sized
223 // and positioned
224 var vp = this._viewportLoc;
225 this.viewportChangeSize(vp.w, vp.h);
226 this.viewportChangePos(0, 0);
227 },
228
229 // Track what parts of the visible canvas that need updating
230 _damage: function(x, y, w, h) {
231 if (x < this._damageBounds.left) {
232 this._damageBounds.left = x;
233 }
234 if (y < this._damageBounds.top) {
235 this._damageBounds.top = y;
236 }
237 if ((x + w) > this._damageBounds.right) {
238 this._damageBounds.right = x + w;
239 }
240 if ((y + h) > this._damageBounds.bottom) {
241 this._damageBounds.bottom = y + h;
242 }
243 },
2ba767a7 244
6d6f0db0
SR
245 // Update the visible canvas with the contents of the
246 // rendering canvas
247 flip: function(from_queue) {
248 if (this._renderQ.length !== 0 && !from_queue) {
249 this._renderQ_push({
250 'type': 'flip'
251 });
252 } else {
253 var x, y, vx, vy, w, h;
2ba767a7 254
6d6f0db0
SR
255 x = this._damageBounds.left;
256 y = this._damageBounds.top;
257 w = this._damageBounds.right - x;
258 h = this._damageBounds.bottom - y;
2ba767a7 259
6d6f0db0
SR
260 vx = x - this._viewportLoc.x;
261 vy = y - this._viewportLoc.y;
1e13775b 262
6d6f0db0
SR
263 if (vx < 0) {
264 w += vx;
265 x -= vx;
266 vx = 0;
84cd0e71 267 }
6d6f0db0
SR
268 if (vy < 0) {
269 h += vy;
270 y -= vy;
271 vy = 0;
84cd0e71 272 }
6d6f0db0
SR
273
274 if ((vx + w) > this._viewportLoc.w) {
275 w = this._viewportLoc.w - vx;
84cd0e71 276 }
6d6f0db0
SR
277 if ((vy + h) > this._viewportLoc.h) {
278 h = this._viewportLoc.h - vy;
84cd0e71 279 }
84cd0e71 280
6d6f0db0
SR
281 if ((w > 0) && (h > 0)) {
282 // FIXME: We may need to disable image smoothing here
283 // as well (see copyImage()), but we haven't
284 // noticed any problem yet.
285 this._targetCtx.drawImage(this._backbuffer,
286 x, y, w, h,
287 vx, vy, w, h);
288 }
84cd0e71 289
6d6f0db0
SR
290 this._damageBounds.left = this._damageBounds.top = 65535;
291 this._damageBounds.right = this._damageBounds.bottom = 0;
292 }
293 },
84cd0e71 294
6d6f0db0
SR
295 clear: function () {
296 if (this._logo) {
297 this.resize(this._logo.width, this._logo.height);
298 this.imageRect(0, 0, this._logo.type, this._logo.data);
299 } else {
300 this.resize(240, 20);
301 this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
302 }
303 this.flip();
304 },
84cd0e71 305
6d6f0db0
SR
306 pending: function() {
307 return this._renderQ.length > 0;
308 },
84cd0e71 309
6d6f0db0
SR
310 flush: function() {
311 if (this._renderQ.length === 0) {
312 this._onFlush();
313 } else {
314 this._flushing = true;
315 }
316 },
2ba767a7 317
6d6f0db0
SR
318 fillRect: function (x, y, width, height, color, from_queue) {
319 if (this._renderQ.length !== 0 && !from_queue) {
320 this._renderQ_push({
321 'type': 'fill',
322 'x': x,
323 'y': y,
324 'width': width,
325 'height': height,
326 'color': color
327 });
328 } else {
329 this._setFillColor(color);
330 this._drawCtx.fillRect(x, y, width, height);
331 this._damage(x, y, width, height);
332 }
333 },
1e13775b 334
6d6f0db0
SR
335 copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
336 if (this._renderQ.length !== 0 && !from_queue) {
337 this._renderQ_push({
338 'type': 'copy',
339 'old_x': old_x,
340 'old_y': old_y,
341 'x': new_x,
342 'y': new_y,
343 'width': w,
344 'height': h,
345 });
346 } else {
347 // Due to this bug among others [1] we need to disable the image-smoothing to
348 // avoid getting a blur effect when copying data.
349 //
350 // 1. https://bugzilla.mozilla.org/show_bug.cgi?id=1194719
351 //
352 // We need to set these every time since all properties are reset
353 // when the the size is changed
354 this._drawCtx.mozImageSmoothingEnabled = false;
355 this._drawCtx.webkitImageSmoothingEnabled = false;
356 this._drawCtx.msImageSmoothingEnabled = false;
357 this._drawCtx.imageSmoothingEnabled = false;
358
359 this._drawCtx.drawImage(this._backbuffer,
360 old_x, old_y, w, h,
361 new_x, new_y, w, h);
362 this._damage(new_x, new_y, w, h);
363 }
364 },
365
366 imageRect: function(x, y, mime, arr) {
367 var img = new Image();
368 img.src = "data: " + mime + ";base64," + Base64.encode(arr);
369 this._renderQ_push({
370 'type': 'img',
371 'img': img,
372 'x': x,
373 'y': y
374 });
375 },
376
377 // start updating a tile
378 startTile: function (x, y, width, height, color) {
379 this._tile_x = x;
380 this._tile_y = y;
381 if (width === 16 && height === 16) {
382 this._tile = this._tile16x16;
383 } else {
384 this._tile = this._drawCtx.createImageData(width, height);
385 }
d9ca5e5b 386
6d6f0db0 387 if (this._prefer_js) {
26586b9d
PO
388 var red = color[2];
389 var green = color[1];
390 var blue = color[0];
6d6f0db0
SR
391
392 var data = this._tile.data;
393 for (var i = 0; i < width * height * 4; i += 4) {
394 data[i] = red;
395 data[i + 1] = green;
396 data[i + 2] = blue;
397 data[i + 3] = 255;
f00193e0 398 }
6d6f0db0
SR
399 } else {
400 this.fillRect(x, y, width, height, color, true);
401 }
402 },
403
404 // update sub-rectangle of the current tile
405 subTile: function (x, y, w, h, color) {
406 if (this._prefer_js) {
26586b9d
PO
407 var red = color[2];
408 var green = color[1];
409 var blue = color[0];
6d6f0db0
SR
410 var xend = x + w;
411 var yend = y + h;
412
413 var data = this._tile.data;
414 var width = this._tile.width;
415 for (var j = y; j < yend; j++) {
416 for (var i = x; i < xend; i++) {
417 var p = (i + (j * width)) * 4;
418 data[p] = red;
419 data[p + 1] = green;
420 data[p + 2] = blue;
421 data[p + 3] = 255;
422 }
f00193e0 423 }
6d6f0db0
SR
424 } else {
425 this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true);
426 }
427 },
428
429 // draw the current tile to the screen
430 finishTile: function () {
431 if (this._prefer_js) {
432 this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
433 this._damage(this._tile_x, this._tile_y,
434 this._tile.width, this._tile.height);
435 }
436 // else: No-op -- already done by setSubTile
437 },
438
439 blitImage: function (x, y, width, height, arr, offset, from_queue) {
440 if (this._renderQ.length !== 0 && !from_queue) {
441 // NB(directxman12): it's technically more performant here to use preallocated arrays,
442 // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
443 // this probably isn't getting called *nearly* as much
444 var new_arr = new Uint8Array(width * height * 4);
445 new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
1578fa68 446 this._renderQ_push({
6d6f0db0
SR
447 'type': 'blit',
448 'data': new_arr,
1578fa68 449 'x': x,
6d6f0db0
SR
450 'y': y,
451 'width': width,
452 'height': height,
1578fa68 453 });
6d6f0db0 454 } else {
26586b9d 455 this._bgrxImageData(x, y, width, height, arr, offset);
6d6f0db0
SR
456 }
457 },
458
459 blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
460 if (this._renderQ.length !== 0 && !from_queue) {
461 // NB(directxman12): it's technically more performant here to use preallocated arrays,
462 // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
463 // this probably isn't getting called *nearly* as much
464 var new_arr = new Uint8Array(width * height * 3);
465 new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
466 this._renderQ_push({
467 'type': 'blitRgb',
468 'data': new_arr,
469 'x': x,
470 'y': y,
471 'width': width,
472 'height': height,
473 });
6d6f0db0 474 } else {
26586b9d 475 this._rgbImageData(x, y, width, height, arr, offset);
6d6f0db0
SR
476 }
477 },
478
479 blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
480 if (this._renderQ.length !== 0 && !from_queue) {
481 // NB(directxman12): it's technically more performant here to use preallocated arrays,
482 // but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
483 // this probably isn't getting called *nearly* as much
484 var new_arr = new Uint8Array(width * height * 4);
485 new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
486 this._renderQ_push({
487 'type': 'blitRgbx',
488 'data': new_arr,
489 'x': x,
490 'y': y,
491 'width': width,
492 'height': height,
493 });
494 } else {
495 this._rgbxImageData(x, y, width, height, arr, offset);
496 }
497 },
1e13775b 498
6d6f0db0
SR
499 drawImage: function (img, x, y) {
500 this._drawCtx.drawImage(img, x, y);
501 this._damage(x, y, img.width, img.height);
502 },
d1800d09 503
6d6f0db0
SR
504 changeCursor: function (pixels, mask, hotx, hoty, w, h) {
505 if (this._cursor_uri === false) {
506 Log.Warn("changeCursor called but no cursor data URI support");
507 return;
508 }
1e13775b 509
26586b9d 510 Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
6d6f0db0
SR
511 },
512
513 defaultCursor: function () {
514 this._target.style.cursor = "default";
515 },
516
517 disableLocalCursor: function () {
518 this._target.style.cursor = "none";
519 },
520
521 clippingDisplay: function () {
522 var vp = this._viewportLoc;
523 return this._fb_width > vp.w || this._fb_height > vp.h;
524 },
525
526 // Overridden getters/setters
527 set_scale: function (scale) {
528 this._rescale(scale);
529 },
530
531 set_viewport: function (viewport) {
532 this._viewport = viewport;
533 // May need to readjust the viewport dimensions
534 var vp = this._viewportLoc;
535 this.viewportChangeSize(vp.w, vp.h);
536 this.viewportChangePos(0, 0);
537 },
538
539 get_width: function () {
540 return this._fb_width;
541 },
542 get_height: function () {
543 return this._fb_height;
544 },
545
546 autoscale: function (containerWidth, containerHeight, downscaleOnly) {
547 var vp = this._viewportLoc;
548 var targetAspectRatio = containerWidth / containerHeight;
549 var fbAspectRatio = vp.w / vp.h;
550
551 var scaleRatio;
552 if (fbAspectRatio >= targetAspectRatio) {
553 scaleRatio = containerWidth / vp.w;
554 } else {
555 scaleRatio = containerHeight / vp.h;
556 }
d3796c14 557
6d6f0db0
SR
558 if (scaleRatio > 1.0 && downscaleOnly) {
559 scaleRatio = 1.0;
560 }
9a23006e 561
6d6f0db0
SR
562 this._rescale(scaleRatio);
563 },
564
565 // Private Methods
566 _rescale: function (factor) {
567 this._scale = factor;
568 var vp = this._viewportLoc;
569
570 // NB(directxman12): If you set the width directly, or set the
571 // style width to a number, the canvas is cleared.
572 // However, if you set the style width to a string
573 // ('NNNpx'), the canvas is scaled without clearing.
574 var width = Math.round(factor * vp.w) + 'px';
575 var height = Math.round(factor * vp.h) + 'px';
576
577 if ((this._target.style.width !== width) ||
578 (this._target.style.height !== height)) {
579 this._target.style.width = width;
580 this._target.style.height = height;
581 }
582 },
9a23006e 583
6d6f0db0 584 _setFillColor: function (color) {
26586b9d 585 var newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
6d6f0db0
SR
586 if (newStyle !== this._prevDrawStyle) {
587 this._drawCtx.fillStyle = newStyle;
588 this._prevDrawStyle = newStyle;
589 }
590 },
591
592 _rgbImageData: function (x, y, width, height, arr, offset) {
593 var img = this._drawCtx.createImageData(width, height);
594 var data = img.data;
595 for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
596 data[i] = arr[j];
597 data[i + 1] = arr[j + 1];
598 data[i + 2] = arr[j + 2];
599 data[i + 3] = 255; // Alpha
600 }
601 this._drawCtx.putImageData(img, x, y);
602 this._damage(x, y, img.width, img.height);
603 },
604
605 _bgrxImageData: function (x, y, width, height, arr, offset) {
606 var img = this._drawCtx.createImageData(width, height);
607 var data = img.data;
608 for (var i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
609 data[i] = arr[j + 2];
610 data[i + 1] = arr[j + 1];
611 data[i + 2] = arr[j];
612 data[i + 3] = 255; // Alpha
613 }
614 this._drawCtx.putImageData(img, x, y);
615 this._damage(x, y, img.width, img.height);
616 },
617
618 _rgbxImageData: function (x, y, width, height, arr, offset) {
619 // NB(directxman12): arr must be an Type Array view
620 var img;
621 if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
622 img = new ImageData(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4), width, height);
623 } else {
624 img = this._drawCtx.createImageData(width, height);
625 img.data.set(new Uint8ClampedArray(arr.buffer, arr.byteOffset, width * height * 4));
626 }
627 this._drawCtx.putImageData(img, x, y);
628 this._damage(x, y, img.width, img.height);
629 },
630
6d6f0db0
SR
631 _renderQ_push: function (action) {
632 this._renderQ.push(action);
633 if (this._renderQ.length === 1) {
634 // If this can be rendered immediately it will be, otherwise
635 // the scanner will wait for the relevant event
636 this._scan_renderQ();
637 }
638 },
639
640 _resume_renderQ: function() {
641 // "this" is the object that is ready, not the
642 // display object
643 this.removeEventListener('load', this._noVNC_display._resume_renderQ);
644 this._noVNC_display._scan_renderQ();
645 },
646
647 _scan_renderQ: function () {
648 var ready = true;
649 while (ready && this._renderQ.length > 0) {
650 var a = this._renderQ[0];
651 switch (a.type) {
652 case 'flip':
653 this.flip(true);
654 break;
655 case 'copy':
656 this.copyImage(a.old_x, a.old_y, a.x, a.y, a.width, a.height, true);
657 break;
658 case 'fill':
659 this.fillRect(a.x, a.y, a.width, a.height, a.color, true);
660 break;
661 case 'blit':
662 this.blitImage(a.x, a.y, a.width, a.height, a.data, 0, true);
663 break;
664 case 'blitRgb':
665 this.blitRgbImage(a.x, a.y, a.width, a.height, a.data, 0, true);
666 break;
667 case 'blitRgbx':
668 this.blitRgbxImage(a.x, a.y, a.width, a.height, a.data, 0, true);
669 break;
670 case 'img':
671 if (a.img.complete) {
672 this.drawImage(a.img, a.x, a.y);
673 } else {
674 a.img._noVNC_display = this;
675 a.img.addEventListener('load', this._resume_renderQ);
676 // We need to wait for this image to 'load'
677 // to keep things in-order
678 ready = false;
679 }
680 break;
1e13775b
SR
681 }
682
6d6f0db0
SR
683 if (ready) {
684 this._renderQ.shift();
1578fa68 685 }
6d6f0db0 686 }
1e13775b 687
6d6f0db0
SR
688 if (this._renderQ.length === 0 && this._flushing) {
689 this._flushing = false;
690 this._onFlush();
691 }
692 },
693};
d9ca5e5b 694
6d6f0db0 695make_properties(Display, [
6d6f0db0
SR
696 ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only)
697 ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data}
6d6f0db0
SR
698 ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0
699 ['viewport', 'rw', 'bool'], // Use viewport clipping
700 ['width', 'ro', 'int'], // Display area width
701 ['height', 'ro', 'int'], // Display area height
702
703 ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
704
705 ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods
706 ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI
707
708 ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished
709]);
710
711// Class Methods
26586b9d 712Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
38170d24
PO
713 if ((w === 0) || (h === 0)) {
714 target.style.cursor = 'none';
715 return;
6d6f0db0
SR
716 }
717
38170d24 718 var cur = []
6d6f0db0 719 var y, x;
38170d24 720 for (y = 0; y < h; y++) {
6d6f0db0 721 for (x = 0; x < w; x++) {
38170d24
PO
722 var idx = y * Math.ceil(w / 8) + Math.floor(x / 8);
723 var alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0;
26586b9d
PO
724 idx = ((w * y) + x) * 4;
725 cur.push(pixels[idx + 2]); // red
726 cur.push(pixels[idx + 1]); // green
727 cur.push(pixels[idx]); // blue
728 cur.push(alpha); // alpha
2c2b492c 729 }
6d6f0db0 730 }
2c2b492c 731
38170d24
PO
732 var canvas = document.createElement('canvas');
733 var ctx = canvas.getContext('2d');
9a23006e 734
38170d24
PO
735 canvas.width = w;
736 canvas.height = h;
737
738 var img;
739 if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
740 img = new ImageData(new Uint8ClampedArray(cur), w, h);
741 } else {
742 img = ctx.createImageData(w, h);
743 img.data.set(new Uint8ClampedArray(cur));
6d6f0db0 744 }
38170d24
PO
745 ctx.clearRect(0, 0, w, h);
746 ctx.putImageData(img, 0, 0);
2c2b492c 747
38170d24 748 var url = canvas.toDataURL();
6d6f0db0
SR
749 target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
750};