]>
Commit | Line | Data |
---|---|---|
c4164bda JM |
1 | /* |
2 | * noVNC: HTML5 VNC client | |
af6b17ce JM |
3 | * Copyright (C) 2010 Joel Martin |
4 | * Licensed under LGPL-3 (see LICENSE.LGPL-3) | |
c4164bda JM |
5 | * |
6 | * See README.md for usage and integration instructions. | |
7 | */ | |
c4164bda | 8 | |
15046f00 JM |
9 | "use strict"; |
10 | /*jslint white: false, bitwise: false */ | |
a7a89626 | 11 | /*global window, $, Util, Base64 */ |
c4164bda | 12 | |
a7a89626 JM |
13 | // Globals defined here |
14 | var Canvas; | |
d93d3e09 | 15 | |
c4164bda | 16 | // Everything namespaced inside Canvas |
d93d3e09 | 17 | Canvas = { |
c8460b03 | 18 | |
2c2b492c JM |
19 | prefer_js : false, // make private |
20 | force_canvas : false, // make private | |
da6dd893 | 21 | cursor_uri : true, // make private |
97763d0e | 22 | |
d41c33e4 JM |
23 | true_color : false, |
24 | colourMap : [], | |
25 | ||
c8460b03 JM |
26 | c_wx : 0, |
27 | c_wy : 0, | |
64ab5c4d | 28 | ctx : null, |
c8460b03 | 29 | |
48ebcdb1 JM |
30 | prevStyle: "", |
31 | ||
15046f00 | 32 | focused : true, |
e2e7c224 JM |
33 | keyPress : null, |
34 | mouseButton : null, | |
35 | mouseMove : null, | |
36 | ||
e2e7c224 | 37 | onMouseButton: function(e, down) { |
61dd52c9 | 38 | var evt, pos, bmask; |
da6dd893 JM |
39 | if (! Canvas.focused) { |
40 | return true; | |
41 | } | |
15046f00 JM |
42 | evt = (e ? e : window.event); |
43 | pos = Util.getEventPosition(e, $(Canvas.id)); | |
e2e7c224 | 44 | bmask = 1 << evt.button; |
81e5adaf | 45 | //Util.Debug('mouse ' + pos.x + "," + pos.y + " down: " + down + " bmask: " + bmask); |
e2e7c224 | 46 | if (Canvas.mouseButton) { |
61dd52c9 | 47 | Canvas.mouseButton(pos.x, pos.y, down, bmask); |
e2e7c224 | 48 | } |
15046f00 | 49 | Util.stopEvent(e); |
e2e7c224 | 50 | return false; |
c8460b03 | 51 | }, |
f272267b | 52 | |
e2e7c224 JM |
53 | onMouseDown: function (e) { |
54 | Canvas.onMouseButton(e, 1); | |
c8460b03 | 55 | }, |
f272267b | 56 | |
e2e7c224 JM |
57 | onMouseUp: function (e) { |
58 | Canvas.onMouseButton(e, 0); | |
8cf20615 JM |
59 | }, |
60 | ||
e2e7c224 | 61 | onMouseWheel: function (e) { |
15046f00 JM |
62 | var evt, pos, bmask, wheelData; |
63 | evt = (e ? e : window.event); | |
64 | pos = Util.getEventPosition(e, $(Canvas.id)); | |
65 | wheelData = evt.detail ? evt.detail * -1 : evt.wheelDelta / 40; | |
e2e7c224 JM |
66 | if (wheelData > 0) { |
67 | bmask = 1 << 3; | |
68 | } else { | |
69 | bmask = 1 << 4; | |
70 | } | |
81e5adaf | 71 | //Util.Debug('mouse scroll by ' + wheelData + ':' + pos.x + "," + pos.y); |
e2e7c224 | 72 | if (Canvas.mouseButton) { |
61dd52c9 JM |
73 | Canvas.mouseButton(pos.x, pos.y, 1, bmask); |
74 | Canvas.mouseButton(pos.x, pos.y, 0, bmask); | |
e2e7c224 | 75 | } |
15046f00 | 76 | Util.stopEvent(e); |
e2e7c224 | 77 | return false; |
a575a383 JM |
78 | }, |
79 | ||
8cf20615 | 80 | |
e2e7c224 | 81 | onMouseMove: function (e) { |
61dd52c9 | 82 | var evt, pos; |
15046f00 JM |
83 | evt = (e ? e : window.event); |
84 | pos = Util.getEventPosition(e, $(Canvas.id)); | |
81e5adaf | 85 | //Util.Debug('mouse ' + evt.which + '/' + evt.button + ' up:' + pos.x + "," + pos.y); |
61dd52c9 JM |
86 | if (Canvas.mouseMove) { |
87 | Canvas.mouseMove(pos.x, pos.y); | |
e2e7c224 JM |
88 | } |
89 | }, | |
90 | ||
91 | onKeyDown: function (e) { | |
81e5adaf | 92 | //Util.Debug("keydown: " + Canvas.getKeysym(e)); |
15046f00 JM |
93 | if (! Canvas.focused) { |
94 | return true; | |
95 | } | |
e2e7c224 JM |
96 | if (Canvas.keyPress) { |
97 | Canvas.keyPress(Canvas.getKeysym(e), 1); | |
98 | } | |
15046f00 | 99 | Util.stopEvent(e); |
e2e7c224 | 100 | return false; |
c8460b03 | 101 | }, |
f272267b | 102 | |
e2e7c224 | 103 | onKeyUp : function (e) { |
81e5adaf | 104 | //Util.Debug("keyup: " + Canvas.getKeysym(e)); |
15046f00 JM |
105 | if (! Canvas.focused) { |
106 | return true; | |
107 | } | |
e2e7c224 JM |
108 | if (Canvas.keyPress) { |
109 | Canvas.keyPress(Canvas.getKeysym(e), 0); | |
110 | } | |
15046f00 | 111 | Util.stopEvent(e); |
e2e7c224 | 112 | return false; |
c8460b03 | 113 | }, |
f272267b | 114 | |
e2e7c224 | 115 | onMouseDisable: function (e) { |
61dd52c9 | 116 | var evt, pos; |
da6dd893 JM |
117 | if (! Canvas.focused) { |
118 | return true; | |
119 | } | |
15046f00 JM |
120 | evt = (e ? e : window.event); |
121 | pos = Util.getPosition($(Canvas.id)); | |
f272267b | 122 | /* Stop propagation if inside canvas area */ |
61dd52c9 JM |
123 | if ((evt.clientX >= pos.x) && |
124 | (evt.clientY >= pos.y) && | |
125 | (evt.clientX < (pos.x + Canvas.c_wx)) && | |
126 | (evt.clientY < (pos.y + Canvas.c_wy))) { | |
81e5adaf | 127 | //Util.Debug("mouse event disabled"); |
15046f00 | 128 | Util.stopEvent(e); |
f272267b | 129 | return false; |
c4164bda | 130 | } |
81e5adaf | 131 | //Util.Debug("mouse event not disabled"); |
15046f00 | 132 | return true; |
c8460b03 | 133 | }, |
f272267b JM |
134 | |
135 | ||
d93d3e09 | 136 | init: function (id) { |
a7a89626 | 137 | var c, imgTest, tval, i, curDat, curSave; |
81e5adaf | 138 | Util.Debug(">> Canvas.init"); |
f272267b | 139 | |
532a9fd9 | 140 | Canvas.id = id; |
d93d3e09 JM |
141 | c = $(Canvas.id); |
142 | ||
d93d3e09 JM |
143 | if (! c.getContext) { throw("No getContext method"); } |
144 | Canvas.ctx = c.getContext('2d'); | |
145 | ||
146 | Canvas.clear(); | |
147 | ||
d93d3e09 | 148 | /* |
48eed1ac JM |
149 | * Determine browser Canvas feature support |
150 | * and select fastest rendering methods | |
d93d3e09 JM |
151 | */ |
152 | tval = 0; | |
48eed1ac | 153 | Canvas.has_imageData = false; |
d93d3e09 JM |
154 | try { |
155 | imgTest = Canvas.ctx.getImageData(0, 0, 1,1); | |
156 | imgTest.data[0] = 123; | |
157 | imgTest.data[3] = 255; | |
158 | Canvas.ctx.putImageData(imgTest, 0, 0); | |
159 | tval = Canvas.ctx.getImageData(0, 0, 1, 1).data[0]; | |
48eed1ac JM |
160 | if (tval === 123) { |
161 | Canvas.has_imageData = true; | |
162 | } | |
163 | } catch (exc) {} | |
164 | ||
165 | if (Canvas.has_imageData) { | |
81e5adaf | 166 | Util.Info("Canvas supports imageData"); |
5235b29d | 167 | Canvas.force_canvas = false; |
d93d3e09 JM |
168 | if (Canvas.ctx.createImageData) { |
169 | // If it's there, it's faster | |
81e5adaf | 170 | Util.Info("Using Canvas createImageData"); |
d93d3e09 JM |
171 | Canvas._imageData = Canvas._imageDataCreate; |
172 | } else if (Canvas.ctx.getImageData) { | |
81e5adaf | 173 | Util.Info("Using Canvas getImageData"); |
d93d3e09 | 174 | Canvas._imageData = Canvas._imageDataGet; |
d93d3e09 | 175 | } |
81e5adaf | 176 | Util.Info("Prefering javascript operations"); |
67134184 | 177 | Canvas.prefer_js = true; |
5235b29d JM |
178 | Canvas._rgbxImage = Canvas._rgbxImageData; |
179 | Canvas._cmapImage = Canvas._cmapImageData; | |
d93d3e09 | 180 | } else { |
81e5adaf | 181 | Util.Warn("Canvas lacks imageData, using fillRect (slow)"); |
5235b29d JM |
182 | Canvas.force_canvas = true; |
183 | Canvas.prefer_js = false; | |
d93d3e09 JM |
184 | Canvas._rgbxImage = Canvas._rgbxImageFill; |
185 | Canvas._cmapImage = Canvas._cmapImageFill; | |
d93d3e09 | 186 | } |
532a9fd9 | 187 | |
2c2b492c JM |
188 | /* |
189 | * Determine browser support for setting the cursor via data URI | |
190 | * scheme | |
191 | */ | |
192 | curDat = []; | |
193 | for (i=0; i < 8 * 8 * 4; i++) { | |
194 | curDat.push(255); | |
195 | } | |
196 | curSave = c.style.cursor; | |
da6dd893 | 197 | Canvas.changeCursor(curDat, curDat, 2, 2, 8, 8); |
2c2b492c JM |
198 | if (c.style.cursor) { |
199 | Util.Info("Data URI scheme cursor supported"); | |
200 | } else { | |
201 | Canvas.cursor_uri = false; | |
202 | Util.Warn("Data URI scheme cursor not supported"); | |
203 | } | |
204 | c.style.cursor = curSave; | |
205 | ||
206 | ||
d93d3e09 JM |
207 | Canvas.colourMap = []; |
208 | Canvas.prevStyle = ""; | |
209 | Canvas.focused = true; | |
210 | ||
81e5adaf | 211 | Util.Debug("<< Canvas.init"); |
d93d3e09 JM |
212 | return true; |
213 | }, | |
214 | ||
215 | ||
216 | start: function (keyPress, mouseButton, mouseMove) { | |
217 | var c; | |
81e5adaf | 218 | Util.Debug(">> Canvas.start"); |
d93d3e09 JM |
219 | |
220 | c = $(Canvas.id); | |
e2e7c224 JM |
221 | Canvas.keyPress = keyPress || null; |
222 | Canvas.mouseButton = mouseButton || null; | |
223 | Canvas.mouseMove = mouseMove || null; | |
64ab5c4d | 224 | |
15046f00 JM |
225 | Util.addEvent(document, 'keydown', Canvas.onKeyDown); |
226 | Util.addEvent(document, 'keyup', Canvas.onKeyUp); | |
227 | Util.addEvent(c, 'mousedown', Canvas.onMouseDown); | |
228 | Util.addEvent(c, 'mouseup', Canvas.onMouseUp); | |
229 | Util.addEvent(c, 'mousemove', Canvas.onMouseMove); | |
230 | Util.addEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', | |
231 | Canvas.onMouseWheel); | |
f272267b JM |
232 | |
233 | /* Work around right and middle click browser behaviors */ | |
15046f00 JM |
234 | Util.addEvent(document, 'click', Canvas.onMouseDisable); |
235 | Util.addEvent(document.body, 'contextmenu', Canvas.onMouseDisable); | |
f272267b | 236 | |
81e5adaf | 237 | Util.Debug("<< Canvas.start"); |
532a9fd9 JM |
238 | }, |
239 | ||
240 | clear: function () { | |
4b4496ad | 241 | Canvas.resize(640, 20); |
d93d3e09 | 242 | Canvas.ctx.clearRect(0, 0, Canvas.c_wx, Canvas.c_wy); |
4b4496ad JM |
243 | }, |
244 | ||
d93d3e09 | 245 | resize: function (width, height, true_color) { |
31af85b9 | 246 | var c = $(Canvas.id); |
d93d3e09 JM |
247 | |
248 | if (typeof true_color !== "undefined") { | |
249 | Canvas.true_color = true_color; | |
250 | } | |
251 | ||
4b4496ad JM |
252 | c.width = width; |
253 | c.height = height; | |
d93d3e09 JM |
254 | |
255 | Canvas.c_wx = c.offsetWidth; | |
256 | Canvas.c_wy = c.offsetHeight; | |
31af85b9 JM |
257 | }, |
258 | ||
259 | stop: function () { | |
532a9fd9 | 260 | var c = $(Canvas.id); |
15046f00 JM |
261 | Util.removeEvent(document, 'keydown', Canvas.onKeyDown); |
262 | Util.removeEvent(document, 'keyup', Canvas.onKeyUp); | |
263 | Util.removeEvent(c, 'mousedown', Canvas.onMouseDown); | |
264 | Util.removeEvent(c, 'mouseup', Canvas.onMouseUp); | |
265 | Util.removeEvent(c, 'mousemove', Canvas.onMouseMove); | |
266 | Util.removeEvent(c, (Util.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel', | |
267 | Canvas.onMouseWheel); | |
2bcb2d5b JM |
268 | |
269 | /* Work around right and middle click browser behaviors */ | |
15046f00 JM |
270 | Util.removeEvent(document, 'click', Canvas.onMouseDisable); |
271 | Util.removeEvent(document.body, 'contextmenu', Canvas.onMouseDisable); | |
2c2b492c JM |
272 | |
273 | // Turn off cursor rendering | |
274 | if (Canvas.cursor_uri) { | |
275 | c.style.cursor = "default"; | |
276 | } | |
532a9fd9 JM |
277 | }, |
278 | ||
3875f847 JM |
279 | /* |
280 | * Tile rendering functions optimized for rendering engines. | |
281 | * | |
282 | * - In Chrome/webkit, Javascript image data array manipulations are | |
283 | * faster than direct Canvas fillStyle, fillRect rendering. In | |
284 | * gecko, Javascript array handling is much slower. | |
285 | */ | |
286 | getTile: function(x, y, width, height, color) { | |
d41c33e4 | 287 | var img, data, p, rgb, red, green, blue, j, i; |
c4164bda JM |
288 | img = {'x': x, 'y': y, 'width': width, 'height': height, |
289 | 'data': []}; | |
97763d0e JM |
290 | if (Canvas.prefer_js) { |
291 | data = img.data; | |
d41c33e4 JM |
292 | if (Canvas.true_color) { |
293 | rgb = color; | |
294 | } else { | |
295 | rgb = Canvas.colourMap[color[0]]; | |
296 | } | |
297 | red = rgb[0]; | |
298 | green = rgb[1]; | |
299 | blue = rgb[2]; | |
15046f00 JM |
300 | for (j = 0; j < height; j += 1) { |
301 | for (i = 0; i < width; i += 1) { | |
3875f847 | 302 | p = (i + (j * width) ) * 4; |
d41c33e4 JM |
303 | data[p + 0] = red; |
304 | data[p + 1] = green; | |
305 | data[p + 2] = blue; | |
306 | //data[p + 3] = 255; // Set Alpha | |
3875f847 JM |
307 | } |
308 | } | |
309 | } else { | |
310 | Canvas.fillRect(x, y, width, height, color); | |
311 | } | |
312 | return img; | |
313 | }, | |
314 | ||
d93d3e09 | 315 | setSubTile: function(img, x, y, w, h, color) { |
d41c33e4 | 316 | var data, p, rgb, red, green, blue, width, j, i; |
97763d0e JM |
317 | if (Canvas.prefer_js) { |
318 | data = img.data; | |
c4164bda | 319 | width = img.width; |
d41c33e4 JM |
320 | if (Canvas.true_color) { |
321 | rgb = color; | |
322 | } else { | |
323 | rgb = Canvas.colourMap[color[0]]; | |
324 | } | |
325 | red = rgb[0]; | |
326 | green = rgb[1]; | |
327 | blue = rgb[2]; | |
15046f00 JM |
328 | for (j = 0; j < h; j += 1) { |
329 | for (i = 0; i < w; i += 1) { | |
3875f847 | 330 | p = (x + i + ((y + j) * width) ) * 4; |
97763d0e JM |
331 | data[p + 0] = red; |
332 | data[p + 1] = green; | |
333 | data[p + 2] = blue; | |
3875f847 JM |
334 | //img.data[p + 3] = 255; // Set Alpha |
335 | } | |
336 | } | |
337 | } else { | |
338 | Canvas.fillRect(img.x + x, img.y + y, w, h, color); | |
339 | } | |
340 | }, | |
341 | ||
342 | putTile: function(img) { | |
97763d0e | 343 | if (Canvas.prefer_js) { |
d93d3e09 | 344 | Canvas._rgbxImage(img.x, img.y, img.width, img.height, img.data, 0); |
3875f847 | 345 | } else { |
d93d3e09 | 346 | // No-op, under gecko already done by setSubTile |
3875f847 JM |
347 | } |
348 | }, | |
349 | ||
d93d3e09 JM |
350 | _imageDataGet: function(width, height) { |
351 | return Canvas.ctx.getImageData(0, 0, width, height); | |
352 | }, | |
353 | _imageDataCreate: function(width, height) { | |
354 | return Canvas.ctx.createImageData(width, height); | |
355 | }, | |
356 | _imageDataRaw: function(width, height) { | |
357 | return {'data': [], 'width': width, 'height': height}; | |
358 | }, | |
3875f847 | 359 | |
d93d3e09 | 360 | _rgbxImageData: function(x, y, width, height, arr, offset) { |
7f4f41b0 | 361 | var img, i, j, data; |
d93d3e09 | 362 | img = Canvas._imageData(width, height); |
97763d0e | 363 | data = img.data; |
d41c33e4 | 364 | for (i=0, j=offset; i < (width * height * 4); i=i+4, j=j+4) { |
7f4f41b0 JM |
365 | data[i + 0] = arr[j + 0]; |
366 | data[i + 1] = arr[j + 1]; | |
367 | data[i + 2] = arr[j + 2]; | |
368 | data[i + 3] = 255; // Set Alpha | |
64ab5c4d JM |
369 | } |
370 | Canvas.ctx.putImageData(img, x, y); | |
d41c33e4 | 371 | }, |
64ab5c4d | 372 | |
d93d3e09 JM |
373 | // really slow fallback if we don't have imageData |
374 | _rgbxImageFill: function(x, y, width, height, arr, offset) { | |
a7a89626 | 375 | var i, j, sx = 0, sy = 0; |
d93d3e09 JM |
376 | for (i=0, j=offset; i < (width * height); i+=1, j+=4) { |
377 | Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j+0], arr[j+1], arr[j+2]]); | |
378 | sx += 1; | |
379 | if ((sx % width) === 0) { | |
380 | sx = 0; | |
381 | sy += 1; | |
382 | } | |
383 | } | |
384 | }, | |
385 | ||
386 | _cmapImageData: function(x, y, width, height, arr, offset) { | |
15046f00 | 387 | var img, i, j, data, rgb, cmap; |
d93d3e09 | 388 | img = Canvas._imageData(width, height); |
d41c33e4 JM |
389 | data = img.data; |
390 | cmap = Canvas.colourMap; | |
d93d3e09 | 391 | for (i=0, j=offset; i < (width * height * 4); i+=4, j+=1) { |
d41c33e4 JM |
392 | rgb = cmap[arr[j]]; |
393 | data[i + 0] = rgb[0]; | |
394 | data[i + 1] = rgb[1]; | |
395 | data[i + 2] = rgb[2]; | |
396 | data[i + 3] = 255; // Set Alpha | |
397 | } | |
398 | Canvas.ctx.putImageData(img, x, y); | |
399 | }, | |
400 | ||
d93d3e09 | 401 | _cmapImageFill: function(x, y, width, height, arr, offset) { |
a7a89626 | 402 | var i, j, sx = 0, sy = 0, cmap; |
d93d3e09 | 403 | cmap = Canvas.colourMap; |
d93d3e09 JM |
404 | for (i=0, j=offset; i < (width * height); i+=1, j+=1) { |
405 | Canvas.fillRect(x+sx, y+sy, 1, 1, [arr[j]]); | |
406 | sx += 1; | |
407 | if ((sx % width) === 0) { | |
408 | sx = 0; | |
409 | sy += 1; | |
410 | } | |
411 | } | |
412 | }, | |
413 | ||
414 | ||
d41c33e4 JM |
415 | blitImage: function(x, y, width, height, arr, offset) { |
416 | if (Canvas.true_color) { | |
d93d3e09 | 417 | Canvas._rgbxImage(x, y, width, height, arr, offset); |
d41c33e4 | 418 | } else { |
d93d3e09 | 419 | Canvas._cmapImage(x, y, width, height, arr, offset); |
d41c33e4 | 420 | } |
d9cbdc7d JM |
421 | }, |
422 | ||
d93d3e09 JM |
423 | blitStringImage: function(str, x, y) { |
424 | var img = new Image(); | |
425 | img.onload = function () { Canvas.ctx.drawImage(img, x, y); }; | |
426 | img.src = str; | |
427 | }, | |
428 | ||
429 | setFillColor: function(color) { | |
d41c33e4 JM |
430 | var rgb, newStyle; |
431 | if (Canvas.true_color) { | |
432 | rgb = color; | |
433 | } else { | |
434 | rgb = Canvas.colourMap[color[0]]; | |
435 | } | |
c4164bda | 436 | if (newStyle !== Canvas.prevStyle) { |
d41c33e4 | 437 | newStyle = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; |
48ebcdb1 | 438 | Canvas.ctx.fillStyle = newStyle; |
c4164bda | 439 | Canvas.prevStyle = newStyle; |
48ebcdb1 | 440 | } |
d93d3e09 JM |
441 | }, |
442 | ||
443 | fillRect: function(x, y, width, height, color) { | |
444 | Canvas.setFillColor(color); | |
ed7e776d JM |
445 | Canvas.ctx.fillRect(x, y, width, height); |
446 | }, | |
447 | ||
48617e27 JM |
448 | copyImage: function(old_x, old_y, new_x, new_y, width, height) { |
449 | Canvas.ctx.drawImage($(Canvas.id), old_x, old_y, width, height, | |
450 | new_x, new_y, width, height); | |
451 | }, | |
452 | ||
d9cbdc7d JM |
453 | /* Translate DOM key event to keysym value */ |
454 | getKeysym: function(e) { | |
c4164bda | 455 | var evt, keysym; |
15046f00 | 456 | evt = (e ? e : window.event); |
d9cbdc7d JM |
457 | |
458 | /* Remap modifier and special keys */ | |
459 | switch ( evt.keyCode ) { | |
460 | case 8 : keysym = 0xFF08; break; // BACKSPACE | |
461 | case 9 : keysym = 0xFF09; break; // TAB | |
462 | case 13 : keysym = 0xFF0D; break; // ENTER | |
463 | case 27 : keysym = 0xFF1B; break; // ESCAPE | |
464 | case 45 : keysym = 0xFF63; break; // INSERT | |
465 | case 46 : keysym = 0xFFFF; break; // DELETE | |
466 | case 36 : keysym = 0xFF50; break; // HOME | |
467 | case 35 : keysym = 0xFF57; break; // END | |
468 | case 33 : keysym = 0xFF55; break; // PAGE_UP | |
469 | case 34 : keysym = 0xFF56; break; // PAGE_DOWN | |
470 | case 37 : keysym = 0xFF51; break; // LEFT | |
471 | case 38 : keysym = 0xFF52; break; // UP | |
472 | case 39 : keysym = 0xFF53; break; // RIGHT | |
473 | case 40 : keysym = 0xFF54; break; // DOWN | |
474 | case 112 : keysym = 0xFFBE; break; // F1 | |
475 | case 113 : keysym = 0xFFBF; break; // F2 | |
476 | case 114 : keysym = 0xFFC0; break; // F3 | |
477 | case 115 : keysym = 0xFFC1; break; // F4 | |
478 | case 116 : keysym = 0xFFC2; break; // F5 | |
479 | case 117 : keysym = 0xFFC3; break; // F6 | |
480 | case 118 : keysym = 0xFFC4; break; // F7 | |
481 | case 119 : keysym = 0xFFC5; break; // F8 | |
482 | case 120 : keysym = 0xFFC6; break; // F9 | |
483 | case 121 : keysym = 0xFFC7; break; // F10 | |
484 | case 122 : keysym = 0xFFC8; break; // F11 | |
485 | case 123 : keysym = 0xFFC9; break; // F12 | |
486 | case 16 : keysym = 0xFFE1; break; // SHIFT | |
487 | case 17 : keysym = 0xFFE3; break; // CONTROL | |
2e041cf2 JM |
488 | //case 18 : keysym = 0xFFE7; break; // Left Meta (Mac Option) |
489 | case 18 : keysym = 0xFFE9; break; // Left ALT (Mac Command) | |
d9cbdc7d JM |
490 | default : keysym = evt.keyCode; break; |
491 | } | |
492 | ||
493 | /* Remap symbols */ | |
494 | switch (keysym) { | |
495 | case 186 : keysym = 59; break; // ; (IE) | |
496 | case 187 : keysym = 61; break; // = (IE) | |
497 | case 188 : keysym = 44; break; // , (Mozilla, IE) | |
9fec75c0 | 498 | case 109 : // - (Mozilla) |
15046f00 | 499 | if (Util.Engine.gecko) { |
c4164bda JM |
500 | keysym = 45; } |
501 | break; | |
d9cbdc7d JM |
502 | case 189 : keysym = 45; break; // - (IE) |
503 | case 190 : keysym = 46; break; // . (Mozilla, IE) | |
504 | case 191 : keysym = 47; break; // / (Mozilla, IE) | |
505 | case 192 : keysym = 96; break; // ` (Mozilla, IE) | |
506 | case 219 : keysym = 91; break; // [ (Mozilla, IE) | |
507 | case 220 : keysym = 92; break; // \ (Mozilla, IE) | |
508 | case 221 : keysym = 93; break; // ] (Mozilla, IE) | |
509 | case 222 : keysym = 39; break; // ' (Mozilla, IE) | |
510 | } | |
511 | ||
512 | /* Remap shifted and unshifted keys */ | |
513 | if (!!evt.shiftKey) { | |
514 | switch (keysym) { | |
515 | case 48 : keysym = 41 ; break; // ) (shifted 0) | |
516 | case 49 : keysym = 33 ; break; // ! (shifted 1) | |
517 | case 50 : keysym = 64 ; break; // @ (shifted 2) | |
518 | case 51 : keysym = 35 ; break; // # (shifted 3) | |
519 | case 52 : keysym = 36 ; break; // $ (shifted 4) | |
520 | case 53 : keysym = 37 ; break; // % (shifted 5) | |
521 | case 54 : keysym = 94 ; break; // ^ (shifted 6) | |
522 | case 55 : keysym = 38 ; break; // & (shifted 7) | |
523 | case 56 : keysym = 42 ; break; // * (shifted 8) | |
524 | case 57 : keysym = 40 ; break; // ( (shifted 9) | |
525 | ||
526 | case 59 : keysym = 58 ; break; // : (shifted `) | |
527 | case 61 : keysym = 43 ; break; // + (shifted ;) | |
528 | case 44 : keysym = 60 ; break; // < (shifted ,) | |
529 | case 45 : keysym = 95 ; break; // _ (shifted -) | |
530 | case 46 : keysym = 62 ; break; // > (shifted .) | |
531 | case 47 : keysym = 63 ; break; // ? (shifted /) | |
532 | case 96 : keysym = 126; break; // ~ (shifted `) | |
533 | case 91 : keysym = 123; break; // { (shifted [) | |
534 | case 92 : keysym = 124; break; // | (shifted \) | |
535 | case 93 : keysym = 125; break; // } (shifted ]) | |
536 | case 39 : keysym = 34 ; break; // " (shifted ') | |
537 | } | |
538 | } else if ((keysym >= 65) && (keysym <=90)) { | |
539 | /* Remap unshifted A-Z */ | |
540 | keysym += 32; | |
541 | } | |
542 | ||
543 | return keysym; | |
2c2b492c JM |
544 | }, |
545 | ||
f272267b | 546 | |
2c2b492c JM |
547 | isCursor: function() { |
548 | return Canvas.cursor_uri; | |
549 | }, | |
da6dd893 | 550 | changeCursor: function(pixels, mask, hotx, hoty, w, h) { |
a7a89626 | 551 | var cur = [], cmap, rgb, IHDRsz, ANDsz, XORsz, url, idx, alpha, x, y; |
da6dd893 | 552 | //Util.Debug(">> changeCursor, x: " + hotx + ", y: " + hoty + ", w: " + w + ", h: " + h); |
2c2b492c JM |
553 | |
554 | if (!Canvas.cursor_uri) { | |
da6dd893 | 555 | Util.Warn("changeCursor called but no cursor data URI support"); |
2c2b492c JM |
556 | return; |
557 | } | |
558 | ||
559 | cmap = Canvas.colourMap; | |
560 | IHDRsz = 40; | |
561 | ANDsz = w * h * 4; | |
562 | XORsz = Math.ceil( (w * h) / 8.0 ); | |
563 | ||
564 | // Main header | |
565 | cur.push16le(0); // Reserved | |
566 | cur.push16le(2); // .CUR type | |
567 | cur.push16le(1); // Number of images, 1 for non-animated ico | |
568 | ||
569 | // Cursor #1 header | |
570 | cur.push(w); // width | |
571 | cur.push(h); // height | |
572 | cur.push(0); // colors, 0 -> true-color | |
573 | cur.push(0); // reserved | |
574 | cur.push16le(hotx); // hotspot x coordinate | |
575 | cur.push16le(hoty); // hotspot y coordinate | |
576 | cur.push32le(IHDRsz + XORsz + ANDsz); // cursor data byte size | |
577 | cur.push32le(22); // offset of cursor data in the file | |
578 | ||
579 | // Cursor #1 InfoHeader | |
580 | cur.push32le(IHDRsz); // Infoheader size | |
581 | cur.push32le(w); // Cursor width | |
582 | cur.push32le(h*2); // XOR+AND height | |
583 | cur.push16le(1); // number of planes | |
584 | cur.push16le(32); // bits per pixel | |
585 | cur.push32le(0); // Type of compression | |
586 | cur.push32le(XORsz + ANDsz); // Size of Image | |
587 | cur.push32le(0); | |
588 | cur.push32le(0); | |
589 | cur.push32le(0); | |
590 | cur.push32le(0); | |
591 | ||
592 | // XOR/color data | |
593 | for (y = h-1; y >= 0; y--) { | |
594 | for (x = 0; x < w; x++) { | |
595 | idx = y * Math.ceil(w / 8) + Math.floor(x/8); | |
596 | alpha = (mask[idx] << (x % 8)) & 0x80 ? 255 : 0; | |
597 | ||
598 | if (Canvas.true_color) { | |
599 | idx = ((w * y) + x) * 4; | |
600 | cur.push(pixels[idx + 2]); // blue | |
601 | cur.push(pixels[idx + 1]); // green | |
602 | cur.push(pixels[idx + 0]); // red | |
603 | cur.push(alpha); // red | |
604 | } else { | |
605 | idx = (w * y) + x; | |
606 | rgb = cmap[pixels[idx]]; | |
607 | cur.push(rgb[2]); // blue | |
608 | cur.push(rgb[1]); // green | |
609 | cur.push(rgb[0]); // red | |
610 | cur.push(alpha); // alpha | |
611 | } | |
612 | } | |
613 | } | |
614 | ||
615 | // AND/bitmask data (ignored, just needs to be right size) | |
616 | for (y = 0; y < h; y++) { | |
617 | for (x = 0; x < Math.ceil(w / 8); x++) { | |
618 | cur.push(0x00); | |
619 | } | |
620 | } | |
621 | ||
622 | url = "data:image/x-icon;base64," + Base64.encode(cur); | |
623 | $(Canvas.id).style.cursor = "url(" + url + ") " + hotx + " " + hoty + ", default"; | |
da6dd893 | 624 | //Util.Debug("<< changeCursor, cur.length: " + cur.length); |
2c2b492c | 625 | } |
d9cbdc7d | 626 | |
c8460b03 JM |
627 | }; |
628 |