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.
- checksums.yaml +7 -0
- data/COPYING +0 -0
- data/LICENSE.txt +0 -0
- data/README.md +0 -0
- data/lib/novnc-rails.rb +8 -0
- data/lib/novnc-rails/version.rb +5 -0
- data/vendor/assets/javascripts/noVNC/Orbitron700.ttf +0 -0
- data/vendor/assets/javascripts/noVNC/Orbitron700.woff +0 -0
- data/vendor/assets/javascripts/noVNC/base.css +512 -0
- data/vendor/assets/javascripts/noVNC/base64.js +113 -0
- data/vendor/assets/javascripts/noVNC/black.css +71 -0
- data/vendor/assets/javascripts/noVNC/blue.css +64 -0
- data/vendor/assets/javascripts/noVNC/des.js +276 -0
- data/vendor/assets/javascripts/noVNC/display.js +751 -0
- data/vendor/assets/javascripts/noVNC/input.js +388 -0
- data/vendor/assets/javascripts/noVNC/jsunzip.js +676 -0
- data/vendor/assets/javascripts/noVNC/keyboard.js +543 -0
- data/vendor/assets/javascripts/noVNC/keysym.js +378 -0
- data/vendor/assets/javascripts/noVNC/keysymdef.js +15 -0
- data/vendor/assets/javascripts/noVNC/logo.js +1 -0
- data/vendor/assets/javascripts/noVNC/playback.js +102 -0
- data/vendor/assets/javascripts/noVNC/rfb.js +1889 -0
- data/vendor/assets/javascripts/noVNC/ui.js +979 -0
- data/vendor/assets/javascripts/noVNC/util.js +656 -0
- data/vendor/assets/javascripts/noVNC/web-socket-js/README.txt +109 -0
- data/vendor/assets/javascripts/noVNC/web-socket-js/WebSocketMain.swf +0 -0
- data/vendor/assets/javascripts/noVNC/web-socket-js/swfobject.js +4 -0
- data/vendor/assets/javascripts/noVNC/web-socket-js/web_socket.js +391 -0
- data/vendor/assets/javascripts/noVNC/websock.js +388 -0
- data/vendor/assets/javascripts/noVNC/webutil.js +239 -0
- 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
|
+
})();
|