1 | /* |
---|
2 | * Licensed to the Apache Software Foundation (ASF) under one |
---|
3 | * or more contributor license agreements. See the NOTICE file |
---|
4 | * distributed with this work for additional information |
---|
5 | * regarding copyright ownership. The ASF licenses this file |
---|
6 | * to you under the Apache License, Version 2.0 (the |
---|
7 | * "License"); you may not use this file except in compliance |
---|
8 | * with the License. You may obtain a copy of the License at |
---|
9 | * |
---|
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
---|
11 | * |
---|
12 | * Unless required by applicable law or agreed to in writing, |
---|
13 | * software distributed under the License is distributed on an |
---|
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
---|
15 | * KIND, either express or implied. See the License for the |
---|
16 | * specific language governing permissions and limitations |
---|
17 | * under the License. |
---|
18 | */ |
---|
19 | |
---|
20 | var Guacamole = Guacamole || {}; |
---|
21 | |
---|
22 | /** |
---|
23 | * Guacamole protocol client. Given a {@link Guacamole.Tunnel}, |
---|
24 | * automatically handles incoming and outgoing Guacamole instructions via the |
---|
25 | * provided tunnel, updating its display using one or more canvas elements. |
---|
26 | * |
---|
27 | * @constructor |
---|
28 | * @param {Guacamole.Tunnel} tunnel The tunnel to use to send and receive |
---|
29 | * Guacamole instructions. |
---|
30 | */ |
---|
31 | Guacamole.Client = function(tunnel) { |
---|
32 | |
---|
33 | var guac_client = this; |
---|
34 | |
---|
35 | var STATE_IDLE = 0; |
---|
36 | var STATE_CONNECTING = 1; |
---|
37 | var STATE_WAITING = 2; |
---|
38 | var STATE_CONNECTED = 3; |
---|
39 | var STATE_DISCONNECTING = 4; |
---|
40 | var STATE_DISCONNECTED = 5; |
---|
41 | |
---|
42 | var currentState = STATE_IDLE; |
---|
43 | |
---|
44 | var currentTimestamp = 0; |
---|
45 | var pingInterval = null; |
---|
46 | |
---|
47 | /** |
---|
48 | * Translation from Guacamole protocol line caps to Layer line caps. |
---|
49 | * @private |
---|
50 | */ |
---|
51 | var lineCap = { |
---|
52 | 0: "butt", |
---|
53 | 1: "round", |
---|
54 | 2: "square" |
---|
55 | }; |
---|
56 | |
---|
57 | /** |
---|
58 | * Translation from Guacamole protocol line caps to Layer line caps. |
---|
59 | * @private |
---|
60 | */ |
---|
61 | var lineJoin = { |
---|
62 | 0: "bevel", |
---|
63 | 1: "miter", |
---|
64 | 2: "round" |
---|
65 | }; |
---|
66 | |
---|
67 | /** |
---|
68 | * The underlying Guacamole display. |
---|
69 | * |
---|
70 | * @private |
---|
71 | * @type {Guacamole.Display} |
---|
72 | */ |
---|
73 | var display = new Guacamole.Display(); |
---|
74 | |
---|
75 | /** |
---|
76 | * All available layers and buffers |
---|
77 | * |
---|
78 | * @private |
---|
79 | * @type {Object.<Number, (Guacamole.Display.VisibleLayer|Guacamole.Layer)>} |
---|
80 | */ |
---|
81 | var layers = {}; |
---|
82 | |
---|
83 | /** |
---|
84 | * All audio players currently in use by the client. Initially, this will |
---|
85 | * be empty, but audio players may be allocated by the server upon request. |
---|
86 | * |
---|
87 | * @private |
---|
88 | * @type {Object.<Number, Guacamole.AudioPlayer>} |
---|
89 | */ |
---|
90 | var audioPlayers = {}; |
---|
91 | |
---|
92 | /** |
---|
93 | * All video players currently in use by the client. Initially, this will |
---|
94 | * be empty, but video players may be allocated by the server upon request. |
---|
95 | * |
---|
96 | * @private |
---|
97 | * @type {Object.<Number, Guacamole.VideoPlayer>} |
---|
98 | */ |
---|
99 | var videoPlayers = {}; |
---|
100 | |
---|
101 | // No initial parsers |
---|
102 | var parsers = []; |
---|
103 | |
---|
104 | // No initial streams |
---|
105 | var streams = []; |
---|
106 | |
---|
107 | /** |
---|
108 | * All current objects. The index of each object is dictated by the |
---|
109 | * Guacamole server. |
---|
110 | * |
---|
111 | * @private |
---|
112 | * @type {Guacamole.Object[]} |
---|
113 | */ |
---|
114 | var objects = []; |
---|
115 | |
---|
116 | // Pool of available stream indices |
---|
117 | var stream_indices = new Guacamole.IntegerPool(); |
---|
118 | |
---|
119 | // Array of allocated output streams by index |
---|
120 | var output_streams = []; |
---|
121 | |
---|
122 | function setState(state) { |
---|
123 | if (state != currentState) { |
---|
124 | currentState = state; |
---|
125 | if (guac_client.onstatechange) |
---|
126 | guac_client.onstatechange(currentState); |
---|
127 | } |
---|
128 | } |
---|
129 | |
---|
130 | function isConnected() { |
---|
131 | return currentState == STATE_CONNECTED |
---|
132 | || currentState == STATE_WAITING; |
---|
133 | } |
---|
134 | |
---|
135 | /** |
---|
136 | * Produces an opaque representation of Guacamole.Client state which can be |
---|
137 | * later imported through a call to importState(). This object is |
---|
138 | * effectively an independent, compressed snapshot of protocol and display |
---|
139 | * state. Invoking this function implicitly flushes the display. |
---|
140 | * |
---|
141 | * @param {function} callback |
---|
142 | * Callback which should be invoked once the state object is ready. The |
---|
143 | * state object will be passed to the callback as the sole parameter. |
---|
144 | * This callback may be invoked immediately, or later as the display |
---|
145 | * finishes rendering and becomes ready. |
---|
146 | */ |
---|
147 | this.exportState = function exportState(callback) { |
---|
148 | |
---|
149 | // Start with empty state |
---|
150 | var state = { |
---|
151 | 'currentState' : currentState, |
---|
152 | 'currentTimestamp' : currentTimestamp, |
---|
153 | 'layers' : {} |
---|
154 | }; |
---|
155 | |
---|
156 | var layersSnapshot = {}; |
---|
157 | |
---|
158 | // Make a copy of all current layers (protocol state) |
---|
159 | for (var key in layers) { |
---|
160 | layersSnapshot[key] = layers[key]; |
---|
161 | } |
---|
162 | |
---|
163 | // Populate layers once data is available (display state, requires flush) |
---|
164 | display.flush(function populateLayers() { |
---|
165 | |
---|
166 | // Export each defined layer/buffer |
---|
167 | for (var key in layersSnapshot) { |
---|
168 | |
---|
169 | var index = parseInt(key); |
---|
170 | var layer = layersSnapshot[key]; |
---|
171 | var canvas = layer.toCanvas(); |
---|
172 | |
---|
173 | // Store layer/buffer dimensions |
---|
174 | var exportLayer = { |
---|
175 | 'width' : layer.width, |
---|
176 | 'height' : layer.height |
---|
177 | }; |
---|
178 | |
---|
179 | // Store layer/buffer image data, if it can be generated |
---|
180 | if (layer.width && layer.height) |
---|
181 | exportLayer.url = canvas.toDataURL('image/png'); |
---|
182 | |
---|
183 | // Add layer properties if not a buffer nor the default layer |
---|
184 | if (index > 0) { |
---|
185 | exportLayer.x = layer.x; |
---|
186 | exportLayer.y = layer.y; |
---|
187 | exportLayer.z = layer.z; |
---|
188 | exportLayer.alpha = layer.alpha; |
---|
189 | exportLayer.matrix = layer.matrix; |
---|
190 | exportLayer.parent = getLayerIndex(layer.parent); |
---|
191 | } |
---|
192 | |
---|
193 | // Store exported layer |
---|
194 | state.layers[key] = exportLayer; |
---|
195 | |
---|
196 | } |
---|
197 | |
---|
198 | // Invoke callback now that the state is ready |
---|
199 | callback(state); |
---|
200 | |
---|
201 | }); |
---|
202 | |
---|
203 | }; |
---|
204 | |
---|
205 | /** |
---|
206 | * Restores Guacamole.Client protocol and display state based on an opaque |
---|
207 | * object from a prior call to exportState(). The Guacamole.Client instance |
---|
208 | * used to export that state need not be the same as this instance. |
---|
209 | * |
---|
210 | * @param {Object} state |
---|
211 | * An opaque representation of Guacamole.Client state from a prior call |
---|
212 | * to exportState(). |
---|
213 | * |
---|
214 | * @param {function} [callback] |
---|
215 | * The function to invoke when state has finished being imported. This |
---|
216 | * may happen immediately, or later as images within the provided state |
---|
217 | * object are loaded. |
---|
218 | */ |
---|
219 | this.importState = function importState(state, callback) { |
---|
220 | |
---|
221 | var key; |
---|
222 | var index; |
---|
223 | |
---|
224 | currentState = state.currentState; |
---|
225 | currentTimestamp = state.currentTimestamp; |
---|
226 | |
---|
227 | // Dispose of all layers |
---|
228 | for (key in layers) { |
---|
229 | index = parseInt(key); |
---|
230 | if (index > 0) |
---|
231 | display.dispose(layers[key]); |
---|
232 | } |
---|
233 | |
---|
234 | layers = {}; |
---|
235 | |
---|
236 | // Import state of each layer/buffer |
---|
237 | for (key in state.layers) { |
---|
238 | |
---|
239 | index = parseInt(key); |
---|
240 | |
---|
241 | var importLayer = state.layers[key]; |
---|
242 | var layer = getLayer(index); |
---|
243 | |
---|
244 | // Reset layer size |
---|
245 | display.resize(layer, importLayer.width, importLayer.height); |
---|
246 | |
---|
247 | // Initialize new layer if it has associated data |
---|
248 | if (importLayer.url) { |
---|
249 | display.setChannelMask(layer, Guacamole.Layer.SRC); |
---|
250 | display.draw(layer, 0, 0, importLayer.url); |
---|
251 | } |
---|
252 | |
---|
253 | // Set layer-specific properties if not a buffer nor the default layer |
---|
254 | if (index > 0 && importLayer.parent >= 0) { |
---|
255 | |
---|
256 | // Apply layer position and set parent |
---|
257 | var parent = getLayer(importLayer.parent); |
---|
258 | display.move(layer, parent, importLayer.x, importLayer.y, importLayer.z); |
---|
259 | |
---|
260 | // Set layer transparency |
---|
261 | display.shade(layer, importLayer.alpha); |
---|
262 | |
---|
263 | // Apply matrix transform |
---|
264 | var matrix = importLayer.matrix; |
---|
265 | display.distort(layer, |
---|
266 | matrix[0], matrix[1], matrix[2], |
---|
267 | matrix[3], matrix[4], matrix[5]); |
---|
268 | |
---|
269 | } |
---|
270 | |
---|
271 | } |
---|
272 | |
---|
273 | // Flush changes to display |
---|
274 | display.flush(callback); |
---|
275 | |
---|
276 | }; |
---|
277 | |
---|
278 | /** |
---|
279 | * Returns the underlying display of this Guacamole.Client. The display |
---|
280 | * contains an Element which can be added to the DOM, causing the |
---|
281 | * display to become visible. |
---|
282 | * |
---|
283 | * @return {Guacamole.Display} The underlying display of this |
---|
284 | * Guacamole.Client. |
---|
285 | */ |
---|
286 | this.getDisplay = function() { |
---|
287 | return display; |
---|
288 | }; |
---|
289 | |
---|
290 | /** |
---|
291 | * Sends the current size of the screen. |
---|
292 | * |
---|
293 | * @param {Number} width The width of the screen. |
---|
294 | * @param {Number} height The height of the screen. |
---|
295 | */ |
---|
296 | this.sendSize = function(width, height) { |
---|
297 | |
---|
298 | // Do not send requests if not connected |
---|
299 | if (!isConnected()) |
---|
300 | return; |
---|
301 | |
---|
302 | tunnel.sendMessage("size", width, height); |
---|
303 | |
---|
304 | }; |
---|
305 | |
---|
306 | /** |
---|
307 | * Sends a key event having the given properties as if the user |
---|
308 | * pressed or released a key. |
---|
309 | * |
---|
310 | * @param {Boolean} pressed Whether the key is pressed (true) or released |
---|
311 | * (false). |
---|
312 | * @param {Number} keysym The keysym of the key being pressed or released. |
---|
313 | */ |
---|
314 | this.sendKeyEvent = function(pressed, keysym) { |
---|
315 | // Do not send requests if not connected |
---|
316 | if (!isConnected()) |
---|
317 | return; |
---|
318 | |
---|
319 | tunnel.sendMessage("key", keysym, pressed); |
---|
320 | }; |
---|
321 | |
---|
322 | /** |
---|
323 | * Sends a mouse event having the properties provided by the given mouse |
---|
324 | * state. |
---|
325 | * |
---|
326 | * @param {Guacamole.Mouse.State} mouseState The state of the mouse to send |
---|
327 | * in the mouse event. |
---|
328 | */ |
---|
329 | this.sendMouseState = function(mouseState) { |
---|
330 | |
---|
331 | // Do not send requests if not connected |
---|
332 | if (!isConnected()) |
---|
333 | return; |
---|
334 | |
---|
335 | // Update client-side cursor |
---|
336 | display.moveCursor( |
---|
337 | Math.floor(mouseState.x), |
---|
338 | Math.floor(mouseState.y) |
---|
339 | ); |
---|
340 | |
---|
341 | // Build mask |
---|
342 | var buttonMask = 0; |
---|
343 | if (mouseState.left) buttonMask |= 1; |
---|
344 | if (mouseState.middle) buttonMask |= 2; |
---|
345 | if (mouseState.right) buttonMask |= 4; |
---|
346 | if (mouseState.up) buttonMask |= 8; |
---|
347 | if (mouseState.down) buttonMask |= 16; |
---|
348 | |
---|
349 | // Send message |
---|
350 | tunnel.sendMessage("mouse", Math.floor(mouseState.x), Math.floor(mouseState.y), buttonMask); |
---|
351 | }; |
---|
352 | |
---|
353 | /** |
---|
354 | * Sets the clipboard of the remote client to the given text data. |
---|
355 | * |
---|
356 | * @deprecated Use createClipboardStream() instead. |
---|
357 | * @param {String} data The data to send as the clipboard contents. |
---|
358 | */ |
---|
359 | this.setClipboard = function(data) { |
---|
360 | |
---|
361 | // Do not send requests if not connected |
---|
362 | if (!isConnected()) |
---|
363 | return; |
---|
364 | |
---|
365 | // Open stream |
---|
366 | var stream = guac_client.createClipboardStream("text/plain"); |
---|
367 | var writer = new Guacamole.StringWriter(stream); |
---|
368 | |
---|
369 | // Send text chunks |
---|
370 | for (var i=0; i<data.length; i += 4096) |
---|
371 | writer.sendText(data.substring(i, i+4096)); |
---|
372 | |
---|
373 | // Close stream |
---|
374 | writer.sendEnd(); |
---|
375 | |
---|
376 | }; |
---|
377 | |
---|
378 | /** |
---|
379 | * Allocates an available stream index and creates a new |
---|
380 | * Guacamole.OutputStream using that index, associating the resulting |
---|
381 | * stream with this Guacamole.Client. Note that this stream will not yet |
---|
382 | * exist as far as the other end of the Guacamole connection is concerned. |
---|
383 | * Streams exist within the Guacamole protocol only when referenced by an |
---|
384 | * instruction which creates the stream, such as a "clipboard", "file", or |
---|
385 | * "pipe" instruction. |
---|
386 | * |
---|
387 | * @returns {Guacamole.OutputStream} |
---|
388 | * A new Guacamole.OutputStream with a newly-allocated index and |
---|
389 | * associated with this Guacamole.Client. |
---|
390 | */ |
---|
391 | this.createOutputStream = function createOutputStream() { |
---|
392 | |
---|
393 | // Allocate index |
---|
394 | var index = stream_indices.next(); |
---|
395 | |
---|
396 | // Return new stream |
---|
397 | var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index); |
---|
398 | return stream; |
---|
399 | |
---|
400 | }; |
---|
401 | |
---|
402 | /** |
---|
403 | * Opens a new audio stream for writing, where audio data having the give |
---|
404 | * mimetype will be sent along the returned stream. The instruction |
---|
405 | * necessary to create this stream will automatically be sent. |
---|
406 | * |
---|
407 | * @param {String} mimetype |
---|
408 | * The mimetype of the audio data that will be sent along the returned |
---|
409 | * stream. |
---|
410 | * |
---|
411 | * @return {Guacamole.OutputStream} |
---|
412 | * The created audio stream. |
---|
413 | */ |
---|
414 | this.createAudioStream = function(mimetype) { |
---|
415 | |
---|
416 | // Allocate and associate stream with audio metadata |
---|
417 | var stream = guac_client.createOutputStream(); |
---|
418 | tunnel.sendMessage("audio", stream.index, mimetype); |
---|
419 | return stream; |
---|
420 | |
---|
421 | }; |
---|
422 | |
---|
423 | /** |
---|
424 | * Opens a new file for writing, having the given index, mimetype and |
---|
425 | * filename. The instruction necessary to create this stream will |
---|
426 | * automatically be sent. |
---|
427 | * |
---|
428 | * @param {String} mimetype The mimetype of the file being sent. |
---|
429 | * @param {String} filename The filename of the file being sent. |
---|
430 | * @return {Guacamole.OutputStream} The created file stream. |
---|
431 | */ |
---|
432 | this.createFileStream = function(mimetype, filename) { |
---|
433 | |
---|
434 | // Allocate and associate stream with file metadata |
---|
435 | var stream = guac_client.createOutputStream(); |
---|
436 | tunnel.sendMessage("file", stream.index, mimetype, filename); |
---|
437 | return stream; |
---|
438 | |
---|
439 | }; |
---|
440 | |
---|
441 | /** |
---|
442 | * Opens a new pipe for writing, having the given name and mimetype. The |
---|
443 | * instruction necessary to create this stream will automatically be sent. |
---|
444 | * |
---|
445 | * @param {String} mimetype The mimetype of the data being sent. |
---|
446 | * @param {String} name The name of the pipe. |
---|
447 | * @return {Guacamole.OutputStream} The created file stream. |
---|
448 | */ |
---|
449 | this.createPipeStream = function(mimetype, name) { |
---|
450 | |
---|
451 | // Allocate and associate stream with pipe metadata |
---|
452 | var stream = guac_client.createOutputStream(); |
---|
453 | tunnel.sendMessage("pipe", stream.index, mimetype, name); |
---|
454 | return stream; |
---|
455 | |
---|
456 | }; |
---|
457 | |
---|
458 | /** |
---|
459 | * Opens a new clipboard object for writing, having the given mimetype. The |
---|
460 | * instruction necessary to create this stream will automatically be sent. |
---|
461 | * |
---|
462 | * @param {String} mimetype The mimetype of the data being sent. |
---|
463 | * @param {String} name The name of the pipe. |
---|
464 | * @return {Guacamole.OutputStream} The created file stream. |
---|
465 | */ |
---|
466 | this.createClipboardStream = function(mimetype) { |
---|
467 | |
---|
468 | // Allocate and associate stream with clipboard metadata |
---|
469 | var stream = guac_client.createOutputStream(); |
---|
470 | tunnel.sendMessage("clipboard", stream.index, mimetype); |
---|
471 | return stream; |
---|
472 | |
---|
473 | }; |
---|
474 | |
---|
475 | /** |
---|
476 | * Creates a new output stream associated with the given object and having |
---|
477 | * the given mimetype and name. The legality of a mimetype and name is |
---|
478 | * dictated by the object itself. The instruction necessary to create this |
---|
479 | * stream will automatically be sent. |
---|
480 | * |
---|
481 | * @param {Number} index |
---|
482 | * The index of the object for which the output stream is being |
---|
483 | * created. |
---|
484 | * |
---|
485 | * @param {String} mimetype |
---|
486 | * The mimetype of the data which will be sent to the output stream. |
---|
487 | * |
---|
488 | * @param {String} name |
---|
489 | * The defined name of an output stream within the given object. |
---|
490 | * |
---|
491 | * @returns {Guacamole.OutputStream} |
---|
492 | * An output stream which will write blobs to the named output stream |
---|
493 | * of the given object. |
---|
494 | */ |
---|
495 | this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) { |
---|
496 | |
---|
497 | // Allocate and ssociate stream with object metadata |
---|
498 | var stream = guac_client.createOutputStream(); |
---|
499 | tunnel.sendMessage("put", index, stream.index, mimetype, name); |
---|
500 | return stream; |
---|
501 | |
---|
502 | }; |
---|
503 | |
---|
504 | /** |
---|
505 | * Requests read access to the input stream having the given name. If |
---|
506 | * successful, a new input stream will be created. |
---|
507 | * |
---|
508 | * @param {Number} index |
---|
509 | * The index of the object from which the input stream is being |
---|
510 | * requested. |
---|
511 | * |
---|
512 | * @param {String} name |
---|
513 | * The name of the input stream to request. |
---|
514 | */ |
---|
515 | this.requestObjectInputStream = function requestObjectInputStream(index, name) { |
---|
516 | |
---|
517 | // Do not send requests if not connected |
---|
518 | if (!isConnected()) |
---|
519 | return; |
---|
520 | |
---|
521 | tunnel.sendMessage("get", index, name); |
---|
522 | }; |
---|
523 | |
---|
524 | /** |
---|
525 | * Acknowledge receipt of a blob on the stream with the given index. |
---|
526 | * |
---|
527 | * @param {Number} index The index of the stream associated with the |
---|
528 | * received blob. |
---|
529 | * @param {String} message A human-readable message describing the error |
---|
530 | * or status. |
---|
531 | * @param {Number} code The error code, if any, or 0 for success. |
---|
532 | */ |
---|
533 | this.sendAck = function(index, message, code) { |
---|
534 | |
---|
535 | // Do not send requests if not connected |
---|
536 | if (!isConnected()) |
---|
537 | return; |
---|
538 | |
---|
539 | tunnel.sendMessage("ack", index, message, code); |
---|
540 | }; |
---|
541 | |
---|
542 | /** |
---|
543 | * Given the index of a file, writes a blob of data to that file. |
---|
544 | * |
---|
545 | * @param {Number} index The index of the file to write to. |
---|
546 | * @param {String} data Base64-encoded data to write to the file. |
---|
547 | */ |
---|
548 | this.sendBlob = function(index, data) { |
---|
549 | |
---|
550 | // Do not send requests if not connected |
---|
551 | if (!isConnected()) |
---|
552 | return; |
---|
553 | |
---|
554 | tunnel.sendMessage("blob", index, data); |
---|
555 | }; |
---|
556 | |
---|
557 | /** |
---|
558 | * Marks a currently-open stream as complete. The other end of the |
---|
559 | * Guacamole connection will be notified via an "end" instruction that the |
---|
560 | * stream is closed, and the index will be made available for reuse in |
---|
561 | * future streams. |
---|
562 | * |
---|
563 | * @param {Number} index |
---|
564 | * The index of the stream to end. |
---|
565 | */ |
---|
566 | this.endStream = function(index) { |
---|
567 | |
---|
568 | // Do not send requests if not connected |
---|
569 | if (!isConnected()) |
---|
570 | return; |
---|
571 | |
---|
572 | // Explicitly close stream by sending "end" instruction |
---|
573 | tunnel.sendMessage("end", index); |
---|
574 | |
---|
575 | // Free associated index and stream if they exist |
---|
576 | if (output_streams[index]) { |
---|
577 | stream_indices.free(index); |
---|
578 | delete output_streams[index]; |
---|
579 | } |
---|
580 | |
---|
581 | }; |
---|
582 | |
---|
583 | /** |
---|
584 | * Fired whenever the state of this Guacamole.Client changes. |
---|
585 | * |
---|
586 | * @event |
---|
587 | * @param {Number} state The new state of the client. |
---|
588 | */ |
---|
589 | this.onstatechange = null; |
---|
590 | |
---|
591 | /** |
---|
592 | * Fired when the remote client sends a name update. |
---|
593 | * |
---|
594 | * @event |
---|
595 | * @param {String} name The new name of this client. |
---|
596 | */ |
---|
597 | this.onname = null; |
---|
598 | |
---|
599 | /** |
---|
600 | * Fired when an error is reported by the remote client, and the connection |
---|
601 | * is being closed. |
---|
602 | * |
---|
603 | * @event |
---|
604 | * @param {Guacamole.Status} status A status object which describes the |
---|
605 | * error. |
---|
606 | */ |
---|
607 | this.onerror = null; |
---|
608 | |
---|
609 | /** |
---|
610 | * Fired when a audio stream is created. The stream provided to this event |
---|
611 | * handler will contain its own event handlers for received data. |
---|
612 | * |
---|
613 | * @event |
---|
614 | * @param {Guacamole.InputStream} stream |
---|
615 | * The stream that will receive audio data from the server. |
---|
616 | * |
---|
617 | * @param {String} mimetype |
---|
618 | * The mimetype of the audio data which will be received. |
---|
619 | * |
---|
620 | * @return {Guacamole.AudioPlayer} |
---|
621 | * An object which implements the Guacamole.AudioPlayer interface and |
---|
622 | * has been initialied to play the data in the provided stream, or null |
---|
623 | * if the built-in audio players of the Guacamole client should be |
---|
624 | * used. |
---|
625 | */ |
---|
626 | this.onaudio = null; |
---|
627 | |
---|
628 | /** |
---|
629 | * Fired when a video stream is created. The stream provided to this event |
---|
630 | * handler will contain its own event handlers for received data. |
---|
631 | * |
---|
632 | * @event |
---|
633 | * @param {Guacamole.InputStream} stream |
---|
634 | * The stream that will receive video data from the server. |
---|
635 | * |
---|
636 | * @param {Guacamole.Display.VisibleLayer} layer |
---|
637 | * The destination layer on which the received video data should be |
---|
638 | * played. It is the responsibility of the Guacamole.VideoPlayer |
---|
639 | * implementation to play the received data within this layer. |
---|
640 | * |
---|
641 | * @param {String} mimetype |
---|
642 | * The mimetype of the video data which will be received. |
---|
643 | * |
---|
644 | * @return {Guacamole.VideoPlayer} |
---|
645 | * An object which implements the Guacamole.VideoPlayer interface and |
---|
646 | * has been initialied to play the data in the provided stream, or null |
---|
647 | * if the built-in video players of the Guacamole client should be |
---|
648 | * used. |
---|
649 | */ |
---|
650 | this.onvideo = null; |
---|
651 | |
---|
652 | /** |
---|
653 | * Fired when the clipboard of the remote client is changing. |
---|
654 | * |
---|
655 | * @event |
---|
656 | * @param {Guacamole.InputStream} stream The stream that will receive |
---|
657 | * clipboard data from the server. |
---|
658 | * @param {String} mimetype The mimetype of the data which will be received. |
---|
659 | */ |
---|
660 | this.onclipboard = null; |
---|
661 | |
---|
662 | /** |
---|
663 | * Fired when a file stream is created. The stream provided to this event |
---|
664 | * handler will contain its own event handlers for received data. |
---|
665 | * |
---|
666 | * @event |
---|
667 | * @param {Guacamole.InputStream} stream The stream that will receive data |
---|
668 | * from the server. |
---|
669 | * @param {String} mimetype The mimetype of the file received. |
---|
670 | * @param {String} filename The name of the file received. |
---|
671 | */ |
---|
672 | this.onfile = null; |
---|
673 | |
---|
674 | /** |
---|
675 | * Fired when a filesystem object is created. The object provided to this |
---|
676 | * event handler will contain its own event handlers and functions for |
---|
677 | * requesting and handling data. |
---|
678 | * |
---|
679 | * @event |
---|
680 | * @param {Guacamole.Object} object |
---|
681 | * The created filesystem object. |
---|
682 | * |
---|
683 | * @param {String} name |
---|
684 | * The name of the filesystem. |
---|
685 | */ |
---|
686 | this.onfilesystem = null; |
---|
687 | |
---|
688 | /** |
---|
689 | * Fired when a pipe stream is created. The stream provided to this event |
---|
690 | * handler will contain its own event handlers for received data; |
---|
691 | * |
---|
692 | * @event |
---|
693 | * @param {Guacamole.InputStream} stream The stream that will receive data |
---|
694 | * from the server. |
---|
695 | * @param {String} mimetype The mimetype of the data which will be received. |
---|
696 | * @param {String} name The name of the pipe. |
---|
697 | */ |
---|
698 | this.onpipe = null; |
---|
699 | |
---|
700 | /** |
---|
701 | * Fired whenever a sync instruction is received from the server, indicating |
---|
702 | * that the server is finished processing any input from the client and |
---|
703 | * has sent any results. |
---|
704 | * |
---|
705 | * @event |
---|
706 | * @param {Number} timestamp The timestamp associated with the sync |
---|
707 | * instruction. |
---|
708 | */ |
---|
709 | this.onsync = null; |
---|
710 | |
---|
711 | /** |
---|
712 | * Returns the layer with the given index, creating it if necessary. |
---|
713 | * Positive indices refer to visible layers, an index of zero refers to |
---|
714 | * the default layer, and negative indices refer to buffers. |
---|
715 | * |
---|
716 | * @private |
---|
717 | * @param {Number} index |
---|
718 | * The index of the layer to retrieve. |
---|
719 | * |
---|
720 | * @return {Guacamole.Display.VisibleLayer|Guacamole.Layer} |
---|
721 | * The layer having the given index. |
---|
722 | */ |
---|
723 | var getLayer = function getLayer(index) { |
---|
724 | |
---|
725 | // Get layer, create if necessary |
---|
726 | var layer = layers[index]; |
---|
727 | if (!layer) { |
---|
728 | |
---|
729 | // Create layer based on index |
---|
730 | if (index === 0) |
---|
731 | layer = display.getDefaultLayer(); |
---|
732 | else if (index > 0) |
---|
733 | layer = display.createLayer(); |
---|
734 | else |
---|
735 | layer = display.createBuffer(); |
---|
736 | |
---|
737 | // Add new layer |
---|
738 | layers[index] = layer; |
---|
739 | |
---|
740 | } |
---|
741 | |
---|
742 | return layer; |
---|
743 | |
---|
744 | }; |
---|
745 | |
---|
746 | /** |
---|
747 | * Returns the index passed to getLayer() when the given layer was created. |
---|
748 | * Positive indices refer to visible layers, an index of zero refers to the |
---|
749 | * default layer, and negative indices refer to buffers. |
---|
750 | * |
---|
751 | * @param {Guacamole.Display.VisibleLayer|Guacamole.Layer} layer |
---|
752 | * The layer whose index should be determined. |
---|
753 | * |
---|
754 | * @returns {Number} |
---|
755 | * The index of the given layer, or null if no such layer is associated |
---|
756 | * with this client. |
---|
757 | */ |
---|
758 | var getLayerIndex = function getLayerIndex(layer) { |
---|
759 | |
---|
760 | // Avoid searching if there clearly is no such layer |
---|
761 | if (!layer) |
---|
762 | return null; |
---|
763 | |
---|
764 | // Search through each layer, returning the index of the given layer |
---|
765 | // once found |
---|
766 | for (var key in layers) { |
---|
767 | if (layer === layers[key]) |
---|
768 | return parseInt(key); |
---|
769 | } |
---|
770 | |
---|
771 | // Otherwise, no such index |
---|
772 | return null; |
---|
773 | |
---|
774 | }; |
---|
775 | |
---|
776 | function getParser(index) { |
---|
777 | |
---|
778 | var parser = parsers[index]; |
---|
779 | |
---|
780 | // If parser not yet created, create it, and tie to the |
---|
781 | // oninstruction handler of the tunnel. |
---|
782 | if (parser == null) { |
---|
783 | parser = parsers[index] = new Guacamole.Parser(); |
---|
784 | parser.oninstruction = tunnel.oninstruction; |
---|
785 | } |
---|
786 | |
---|
787 | return parser; |
---|
788 | |
---|
789 | } |
---|
790 | |
---|
791 | /** |
---|
792 | * Handlers for all defined layer properties. |
---|
793 | * @private |
---|
794 | */ |
---|
795 | var layerPropertyHandlers = { |
---|
796 | |
---|
797 | "miter-limit": function(layer, value) { |
---|
798 | display.setMiterLimit(layer, parseFloat(value)); |
---|
799 | } |
---|
800 | |
---|
801 | }; |
---|
802 | |
---|
803 | /** |
---|
804 | * Handlers for all instruction opcodes receivable by a Guacamole protocol |
---|
805 | * client. |
---|
806 | * @private |
---|
807 | */ |
---|
808 | var instructionHandlers = { |
---|
809 | |
---|
810 | "ack": function(parameters) { |
---|
811 | |
---|
812 | var stream_index = parseInt(parameters[0]); |
---|
813 | var reason = parameters[1]; |
---|
814 | var code = parseInt(parameters[2]); |
---|
815 | |
---|
816 | // Get stream |
---|
817 | var stream = output_streams[stream_index]; |
---|
818 | if (stream) { |
---|
819 | |
---|
820 | // Signal ack if handler defined |
---|
821 | if (stream.onack) |
---|
822 | stream.onack(new Guacamole.Status(code, reason)); |
---|
823 | |
---|
824 | // If code is an error, invalidate stream if not already |
---|
825 | // invalidated by onack handler |
---|
826 | if (code >= 0x0100 && output_streams[stream_index] === stream) { |
---|
827 | stream_indices.free(stream_index); |
---|
828 | delete output_streams[stream_index]; |
---|
829 | } |
---|
830 | |
---|
831 | } |
---|
832 | |
---|
833 | }, |
---|
834 | |
---|
835 | "arc": function(parameters) { |
---|
836 | |
---|
837 | var layer = getLayer(parseInt(parameters[0])); |
---|
838 | var x = parseInt(parameters[1]); |
---|
839 | var y = parseInt(parameters[2]); |
---|
840 | var radius = parseInt(parameters[3]); |
---|
841 | var startAngle = parseFloat(parameters[4]); |
---|
842 | var endAngle = parseFloat(parameters[5]); |
---|
843 | var negative = parseInt(parameters[6]); |
---|
844 | |
---|
845 | display.arc(layer, x, y, radius, startAngle, endAngle, negative != 0); |
---|
846 | |
---|
847 | }, |
---|
848 | |
---|
849 | "audio": function(parameters) { |
---|
850 | |
---|
851 | var stream_index = parseInt(parameters[0]); |
---|
852 | var mimetype = parameters[1]; |
---|
853 | |
---|
854 | // Create stream |
---|
855 | var stream = streams[stream_index] = |
---|
856 | new Guacamole.InputStream(guac_client, stream_index); |
---|
857 | |
---|
858 | // Get player instance via callback |
---|
859 | var audioPlayer = null; |
---|
860 | if (guac_client.onaudio) |
---|
861 | audioPlayer = guac_client.onaudio(stream, mimetype); |
---|
862 | |
---|
863 | // If unsuccessful, try to use a default implementation |
---|
864 | if (!audioPlayer) |
---|
865 | audioPlayer = Guacamole.AudioPlayer.getInstance(stream, mimetype); |
---|
866 | |
---|
867 | // If we have successfully retrieved an audio player, send success response |
---|
868 | if (audioPlayer) { |
---|
869 | audioPlayers[stream_index] = audioPlayer; |
---|
870 | guac_client.sendAck(stream_index, "OK", 0x0000); |
---|
871 | } |
---|
872 | |
---|
873 | // Otherwise, mimetype must be unsupported |
---|
874 | else |
---|
875 | guac_client.sendAck(stream_index, "BAD TYPE", 0x030F); |
---|
876 | |
---|
877 | }, |
---|
878 | |
---|
879 | "blob": function(parameters) { |
---|
880 | |
---|
881 | // Get stream |
---|
882 | var stream_index = parseInt(parameters[0]); |
---|
883 | var data = parameters[1]; |
---|
884 | var stream = streams[stream_index]; |
---|
885 | |
---|
886 | // Write data |
---|
887 | if (stream && stream.onblob) |
---|
888 | stream.onblob(data); |
---|
889 | |
---|
890 | }, |
---|
891 | |
---|
892 | "body" : function handleBody(parameters) { |
---|
893 | |
---|
894 | // Get object |
---|
895 | var objectIndex = parseInt(parameters[0]); |
---|
896 | var object = objects[objectIndex]; |
---|
897 | |
---|
898 | var streamIndex = parseInt(parameters[1]); |
---|
899 | var mimetype = parameters[2]; |
---|
900 | var name = parameters[3]; |
---|
901 | |
---|
902 | // Create stream if handler defined |
---|
903 | if (object && object.onbody) { |
---|
904 | var stream = streams[streamIndex] = new Guacamole.InputStream(guac_client, streamIndex); |
---|
905 | object.onbody(stream, mimetype, name); |
---|
906 | } |
---|
907 | |
---|
908 | // Otherwise, unsupported |
---|
909 | else |
---|
910 | guac_client.sendAck(streamIndex, "Receipt of body unsupported", 0x0100); |
---|
911 | |
---|
912 | }, |
---|
913 | |
---|
914 | "cfill": function(parameters) { |
---|
915 | |
---|
916 | var channelMask = parseInt(parameters[0]); |
---|
917 | var layer = getLayer(parseInt(parameters[1])); |
---|
918 | var r = parseInt(parameters[2]); |
---|
919 | var g = parseInt(parameters[3]); |
---|
920 | var b = parseInt(parameters[4]); |
---|
921 | var a = parseInt(parameters[5]); |
---|
922 | |
---|
923 | display.setChannelMask(layer, channelMask); |
---|
924 | display.fillColor(layer, r, g, b, a); |
---|
925 | |
---|
926 | }, |
---|
927 | |
---|
928 | "clip": function(parameters) { |
---|
929 | |
---|
930 | var layer = getLayer(parseInt(parameters[0])); |
---|
931 | |
---|
932 | display.clip(layer); |
---|
933 | |
---|
934 | }, |
---|
935 | |
---|
936 | "clipboard": function(parameters) { |
---|
937 | |
---|
938 | var stream_index = parseInt(parameters[0]); |
---|
939 | var mimetype = parameters[1]; |
---|
940 | |
---|
941 | // Create stream |
---|
942 | if (guac_client.onclipboard) { |
---|
943 | var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
---|
944 | guac_client.onclipboard(stream, mimetype); |
---|
945 | } |
---|
946 | |
---|
947 | // Otherwise, unsupported |
---|
948 | else |
---|
949 | guac_client.sendAck(stream_index, "Clipboard unsupported", 0x0100); |
---|
950 | |
---|
951 | }, |
---|
952 | |
---|
953 | "close": function(parameters) { |
---|
954 | |
---|
955 | var layer = getLayer(parseInt(parameters[0])); |
---|
956 | |
---|
957 | display.close(layer); |
---|
958 | |
---|
959 | }, |
---|
960 | |
---|
961 | "copy": function(parameters) { |
---|
962 | |
---|
963 | var srcL = getLayer(parseInt(parameters[0])); |
---|
964 | var srcX = parseInt(parameters[1]); |
---|
965 | var srcY = parseInt(parameters[2]); |
---|
966 | var srcWidth = parseInt(parameters[3]); |
---|
967 | var srcHeight = parseInt(parameters[4]); |
---|
968 | var channelMask = parseInt(parameters[5]); |
---|
969 | var dstL = getLayer(parseInt(parameters[6])); |
---|
970 | var dstX = parseInt(parameters[7]); |
---|
971 | var dstY = parseInt(parameters[8]); |
---|
972 | |
---|
973 | display.setChannelMask(dstL, channelMask); |
---|
974 | display.copy(srcL, srcX, srcY, srcWidth, srcHeight, |
---|
975 | dstL, dstX, dstY); |
---|
976 | |
---|
977 | }, |
---|
978 | |
---|
979 | "cstroke": function(parameters) { |
---|
980 | |
---|
981 | var channelMask = parseInt(parameters[0]); |
---|
982 | var layer = getLayer(parseInt(parameters[1])); |
---|
983 | var cap = lineCap[parseInt(parameters[2])]; |
---|
984 | var join = lineJoin[parseInt(parameters[3])]; |
---|
985 | var thickness = parseInt(parameters[4]); |
---|
986 | var r = parseInt(parameters[5]); |
---|
987 | var g = parseInt(parameters[6]); |
---|
988 | var b = parseInt(parameters[7]); |
---|
989 | var a = parseInt(parameters[8]); |
---|
990 | |
---|
991 | display.setChannelMask(layer, channelMask); |
---|
992 | display.strokeColor(layer, cap, join, thickness, r, g, b, a); |
---|
993 | |
---|
994 | }, |
---|
995 | |
---|
996 | "cursor": function(parameters) { |
---|
997 | |
---|
998 | var cursorHotspotX = parseInt(parameters[0]); |
---|
999 | var cursorHotspotY = parseInt(parameters[1]); |
---|
1000 | var srcL = getLayer(parseInt(parameters[2])); |
---|
1001 | var srcX = parseInt(parameters[3]); |
---|
1002 | var srcY = parseInt(parameters[4]); |
---|
1003 | var srcWidth = parseInt(parameters[5]); |
---|
1004 | var srcHeight = parseInt(parameters[6]); |
---|
1005 | |
---|
1006 | display.setCursor(cursorHotspotX, cursorHotspotY, |
---|
1007 | srcL, srcX, srcY, srcWidth, srcHeight); |
---|
1008 | |
---|
1009 | }, |
---|
1010 | |
---|
1011 | "curve": function(parameters) { |
---|
1012 | |
---|
1013 | var layer = getLayer(parseInt(parameters[0])); |
---|
1014 | var cp1x = parseInt(parameters[1]); |
---|
1015 | var cp1y = parseInt(parameters[2]); |
---|
1016 | var cp2x = parseInt(parameters[3]); |
---|
1017 | var cp2y = parseInt(parameters[4]); |
---|
1018 | var x = parseInt(parameters[5]); |
---|
1019 | var y = parseInt(parameters[6]); |
---|
1020 | |
---|
1021 | display.curveTo(layer, cp1x, cp1y, cp2x, cp2y, x, y); |
---|
1022 | |
---|
1023 | }, |
---|
1024 | |
---|
1025 | "disconnect" : function handleDisconnect(parameters) { |
---|
1026 | |
---|
1027 | // Explicitly tear down connection |
---|
1028 | guac_client.disconnect(); |
---|
1029 | |
---|
1030 | }, |
---|
1031 | |
---|
1032 | "dispose": function(parameters) { |
---|
1033 | |
---|
1034 | var layer_index = parseInt(parameters[0]); |
---|
1035 | |
---|
1036 | // If visible layer, remove from parent |
---|
1037 | if (layer_index > 0) { |
---|
1038 | |
---|
1039 | // Remove from parent |
---|
1040 | var layer = getLayer(layer_index); |
---|
1041 | display.dispose(layer); |
---|
1042 | |
---|
1043 | // Delete reference |
---|
1044 | delete layers[layer_index]; |
---|
1045 | |
---|
1046 | } |
---|
1047 | |
---|
1048 | // If buffer, just delete reference |
---|
1049 | else if (layer_index < 0) |
---|
1050 | delete layers[layer_index]; |
---|
1051 | |
---|
1052 | // Attempting to dispose the root layer currently has no effect. |
---|
1053 | |
---|
1054 | }, |
---|
1055 | |
---|
1056 | "distort": function(parameters) { |
---|
1057 | |
---|
1058 | var layer_index = parseInt(parameters[0]); |
---|
1059 | var a = parseFloat(parameters[1]); |
---|
1060 | var b = parseFloat(parameters[2]); |
---|
1061 | var c = parseFloat(parameters[3]); |
---|
1062 | var d = parseFloat(parameters[4]); |
---|
1063 | var e = parseFloat(parameters[5]); |
---|
1064 | var f = parseFloat(parameters[6]); |
---|
1065 | |
---|
1066 | // Only valid for visible layers (not buffers) |
---|
1067 | if (layer_index >= 0) { |
---|
1068 | var layer = getLayer(layer_index); |
---|
1069 | display.distort(layer, a, b, c, d, e, f); |
---|
1070 | } |
---|
1071 | |
---|
1072 | }, |
---|
1073 | |
---|
1074 | "error": function(parameters) { |
---|
1075 | |
---|
1076 | var reason = parameters[0]; |
---|
1077 | var code = parseInt(parameters[1]); |
---|
1078 | |
---|
1079 | // Call handler if defined |
---|
1080 | if (guac_client.onerror) |
---|
1081 | guac_client.onerror(new Guacamole.Status(code, reason)); |
---|
1082 | |
---|
1083 | guac_client.disconnect(); |
---|
1084 | |
---|
1085 | }, |
---|
1086 | |
---|
1087 | "end": function(parameters) { |
---|
1088 | |
---|
1089 | var stream_index = parseInt(parameters[0]); |
---|
1090 | |
---|
1091 | // Get stream |
---|
1092 | var stream = streams[stream_index]; |
---|
1093 | if (stream) { |
---|
1094 | |
---|
1095 | // Signal end of stream if handler defined |
---|
1096 | if (stream.onend) |
---|
1097 | stream.onend(); |
---|
1098 | |
---|
1099 | // Invalidate stream |
---|
1100 | delete streams[stream_index]; |
---|
1101 | |
---|
1102 | } |
---|
1103 | |
---|
1104 | }, |
---|
1105 | |
---|
1106 | "file": function(parameters) { |
---|
1107 | |
---|
1108 | var stream_index = parseInt(parameters[0]); |
---|
1109 | var mimetype = parameters[1]; |
---|
1110 | var filename = parameters[2]; |
---|
1111 | |
---|
1112 | // Create stream |
---|
1113 | if (guac_client.onfile) { |
---|
1114 | var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
---|
1115 | guac_client.onfile(stream, mimetype, filename); |
---|
1116 | } |
---|
1117 | |
---|
1118 | // Otherwise, unsupported |
---|
1119 | else |
---|
1120 | guac_client.sendAck(stream_index, "File transfer unsupported", 0x0100); |
---|
1121 | |
---|
1122 | }, |
---|
1123 | |
---|
1124 | "filesystem" : function handleFilesystem(parameters) { |
---|
1125 | |
---|
1126 | var objectIndex = parseInt(parameters[0]); |
---|
1127 | var name = parameters[1]; |
---|
1128 | |
---|
1129 | // Create object, if supported |
---|
1130 | if (guac_client.onfilesystem) { |
---|
1131 | var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex); |
---|
1132 | guac_client.onfilesystem(object, name); |
---|
1133 | } |
---|
1134 | |
---|
1135 | // If unsupported, simply ignore the availability of the filesystem |
---|
1136 | |
---|
1137 | }, |
---|
1138 | |
---|
1139 | "identity": function(parameters) { |
---|
1140 | |
---|
1141 | var layer = getLayer(parseInt(parameters[0])); |
---|
1142 | |
---|
1143 | display.setTransform(layer, 1, 0, 0, 1, 0, 0); |
---|
1144 | |
---|
1145 | }, |
---|
1146 | |
---|
1147 | "img": function(parameters) { |
---|
1148 | |
---|
1149 | var stream_index = parseInt(parameters[0]); |
---|
1150 | var channelMask = parseInt(parameters[1]); |
---|
1151 | var layer = getLayer(parseInt(parameters[2])); |
---|
1152 | var mimetype = parameters[3]; |
---|
1153 | var x = parseInt(parameters[4]); |
---|
1154 | var y = parseInt(parameters[5]); |
---|
1155 | |
---|
1156 | // Create stream |
---|
1157 | var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
---|
1158 | var reader = new Guacamole.DataURIReader(stream, mimetype); |
---|
1159 | |
---|
1160 | // Draw image when stream is complete |
---|
1161 | reader.onend = function drawImageBlob() { |
---|
1162 | display.setChannelMask(layer, channelMask); |
---|
1163 | display.draw(layer, x, y, reader.getURI()); |
---|
1164 | }; |
---|
1165 | |
---|
1166 | }, |
---|
1167 | |
---|
1168 | "jpeg": function(parameters) { |
---|
1169 | |
---|
1170 | var channelMask = parseInt(parameters[0]); |
---|
1171 | var layer = getLayer(parseInt(parameters[1])); |
---|
1172 | var x = parseInt(parameters[2]); |
---|
1173 | var y = parseInt(parameters[3]); |
---|
1174 | var data = parameters[4]; |
---|
1175 | |
---|
1176 | display.setChannelMask(layer, channelMask); |
---|
1177 | display.draw(layer, x, y, "data:image/jpeg;base64," + data); |
---|
1178 | |
---|
1179 | }, |
---|
1180 | |
---|
1181 | "lfill": function(parameters) { |
---|
1182 | |
---|
1183 | var channelMask = parseInt(parameters[0]); |
---|
1184 | var layer = getLayer(parseInt(parameters[1])); |
---|
1185 | var srcLayer = getLayer(parseInt(parameters[2])); |
---|
1186 | |
---|
1187 | display.setChannelMask(layer, channelMask); |
---|
1188 | display.fillLayer(layer, srcLayer); |
---|
1189 | |
---|
1190 | }, |
---|
1191 | |
---|
1192 | "line": function(parameters) { |
---|
1193 | |
---|
1194 | var layer = getLayer(parseInt(parameters[0])); |
---|
1195 | var x = parseInt(parameters[1]); |
---|
1196 | var y = parseInt(parameters[2]); |
---|
1197 | |
---|
1198 | display.lineTo(layer, x, y); |
---|
1199 | |
---|
1200 | }, |
---|
1201 | |
---|
1202 | "lstroke": function(parameters) { |
---|
1203 | |
---|
1204 | var channelMask = parseInt(parameters[0]); |
---|
1205 | var layer = getLayer(parseInt(parameters[1])); |
---|
1206 | var srcLayer = getLayer(parseInt(parameters[2])); |
---|
1207 | |
---|
1208 | display.setChannelMask(layer, channelMask); |
---|
1209 | display.strokeLayer(layer, srcLayer); |
---|
1210 | |
---|
1211 | }, |
---|
1212 | |
---|
1213 | "mouse" : function handleMouse(parameters) { |
---|
1214 | |
---|
1215 | var x = parseInt(parameters[0]); |
---|
1216 | var y = parseInt(parameters[1]); |
---|
1217 | |
---|
1218 | // Display and move software cursor to received coordinates |
---|
1219 | display.showCursor(true); |
---|
1220 | display.moveCursor(x, y); |
---|
1221 | |
---|
1222 | }, |
---|
1223 | |
---|
1224 | "move": function(parameters) { |
---|
1225 | |
---|
1226 | var layer_index = parseInt(parameters[0]); |
---|
1227 | var parent_index = parseInt(parameters[1]); |
---|
1228 | var x = parseInt(parameters[2]); |
---|
1229 | var y = parseInt(parameters[3]); |
---|
1230 | var z = parseInt(parameters[4]); |
---|
1231 | |
---|
1232 | // Only valid for non-default layers |
---|
1233 | if (layer_index > 0 && parent_index >= 0) { |
---|
1234 | var layer = getLayer(layer_index); |
---|
1235 | var parent = getLayer(parent_index); |
---|
1236 | display.move(layer, parent, x, y, z); |
---|
1237 | } |
---|
1238 | |
---|
1239 | }, |
---|
1240 | |
---|
1241 | "name": function(parameters) { |
---|
1242 | if (guac_client.onname) guac_client.onname(parameters[0]); |
---|
1243 | }, |
---|
1244 | |
---|
1245 | "nest": function(parameters) { |
---|
1246 | var parser = getParser(parseInt(parameters[0])); |
---|
1247 | parser.receive(parameters[1]); |
---|
1248 | }, |
---|
1249 | |
---|
1250 | "pipe": function(parameters) { |
---|
1251 | |
---|
1252 | var stream_index = parseInt(parameters[0]); |
---|
1253 | var mimetype = parameters[1]; |
---|
1254 | var name = parameters[2]; |
---|
1255 | |
---|
1256 | // Create stream |
---|
1257 | if (guac_client.onpipe) { |
---|
1258 | var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index); |
---|
1259 | guac_client.onpipe(stream, mimetype, name); |
---|
1260 | } |
---|
1261 | |
---|
1262 | // Otherwise, unsupported |
---|
1263 | else |
---|
1264 | guac_client.sendAck(stream_index, "Named pipes unsupported", 0x0100); |
---|
1265 | |
---|
1266 | }, |
---|
1267 | |
---|
1268 | "png": function(parameters) { |
---|
1269 | |
---|
1270 | var channelMask = parseInt(parameters[0]); |
---|
1271 | var layer = getLayer(parseInt(parameters[1])); |
---|
1272 | var x = parseInt(parameters[2]); |
---|
1273 | var y = parseInt(parameters[3]); |
---|
1274 | var data = parameters[4]; |
---|
1275 | |
---|
1276 | display.setChannelMask(layer, channelMask); |
---|
1277 | display.draw(layer, x, y, "data:image/png;base64," + data); |
---|
1278 | |
---|
1279 | }, |
---|
1280 | |
---|
1281 | "pop": function(parameters) { |
---|
1282 | |
---|
1283 | var layer = getLayer(parseInt(parameters[0])); |
---|
1284 | |
---|
1285 | display.pop(layer); |
---|
1286 | |
---|
1287 | }, |
---|
1288 | |
---|
1289 | "push": function(parameters) { |
---|
1290 | |
---|
1291 | var layer = getLayer(parseInt(parameters[0])); |
---|
1292 | |
---|
1293 | display.push(layer); |
---|
1294 | |
---|
1295 | }, |
---|
1296 | |
---|
1297 | "rect": function(parameters) { |
---|
1298 | |
---|
1299 | var layer = getLayer(parseInt(parameters[0])); |
---|
1300 | var x = parseInt(parameters[1]); |
---|
1301 | var y = parseInt(parameters[2]); |
---|
1302 | var w = parseInt(parameters[3]); |
---|
1303 | var h = parseInt(parameters[4]); |
---|
1304 | |
---|
1305 | display.rect(layer, x, y, w, h); |
---|
1306 | |
---|
1307 | }, |
---|
1308 | |
---|
1309 | "reset": function(parameters) { |
---|
1310 | |
---|
1311 | var layer = getLayer(parseInt(parameters[0])); |
---|
1312 | |
---|
1313 | display.reset(layer); |
---|
1314 | |
---|
1315 | }, |
---|
1316 | |
---|
1317 | "set": function(parameters) { |
---|
1318 | |
---|
1319 | var layer = getLayer(parseInt(parameters[0])); |
---|
1320 | var name = parameters[1]; |
---|
1321 | var value = parameters[2]; |
---|
1322 | |
---|
1323 | // Call property handler if defined |
---|
1324 | var handler = layerPropertyHandlers[name]; |
---|
1325 | if (handler) |
---|
1326 | handler(layer, value); |
---|
1327 | |
---|
1328 | }, |
---|
1329 | |
---|
1330 | "shade": function(parameters) { |
---|
1331 | |
---|
1332 | var layer_index = parseInt(parameters[0]); |
---|
1333 | var a = parseInt(parameters[1]); |
---|
1334 | |
---|
1335 | // Only valid for visible layers (not buffers) |
---|
1336 | if (layer_index >= 0) { |
---|
1337 | var layer = getLayer(layer_index); |
---|
1338 | display.shade(layer, a); |
---|
1339 | } |
---|
1340 | |
---|
1341 | }, |
---|
1342 | |
---|
1343 | "size": function(parameters) { |
---|
1344 | |
---|
1345 | var layer_index = parseInt(parameters[0]); |
---|
1346 | var layer = getLayer(layer_index); |
---|
1347 | var width = parseInt(parameters[1]); |
---|
1348 | var height = parseInt(parameters[2]); |
---|
1349 | |
---|
1350 | display.resize(layer, width, height); |
---|
1351 | |
---|
1352 | }, |
---|
1353 | |
---|
1354 | "start": function(parameters) { |
---|
1355 | |
---|
1356 | var layer = getLayer(parseInt(parameters[0])); |
---|
1357 | var x = parseInt(parameters[1]); |
---|
1358 | var y = parseInt(parameters[2]); |
---|
1359 | |
---|
1360 | display.moveTo(layer, x, y); |
---|
1361 | |
---|
1362 | }, |
---|
1363 | |
---|
1364 | "sync": function(parameters) { |
---|
1365 | |
---|
1366 | var timestamp = parseInt(parameters[0]); |
---|
1367 | |
---|
1368 | // Flush display, send sync when done |
---|
1369 | display.flush(function displaySyncComplete() { |
---|
1370 | |
---|
1371 | // Synchronize all audio players |
---|
1372 | for (var index in audioPlayers) { |
---|
1373 | var audioPlayer = audioPlayers[index]; |
---|
1374 | if (audioPlayer) |
---|
1375 | audioPlayer.sync(); |
---|
1376 | } |
---|
1377 | |
---|
1378 | // Send sync response to server |
---|
1379 | if (timestamp !== currentTimestamp) { |
---|
1380 | tunnel.sendMessage("sync", timestamp); |
---|
1381 | currentTimestamp = timestamp; |
---|
1382 | } |
---|
1383 | |
---|
1384 | }); |
---|
1385 | |
---|
1386 | // If received first update, no longer waiting. |
---|
1387 | if (currentState === STATE_WAITING) |
---|
1388 | setState(STATE_CONNECTED); |
---|
1389 | |
---|
1390 | // Call sync handler if defined |
---|
1391 | if (guac_client.onsync) |
---|
1392 | guac_client.onsync(timestamp); |
---|
1393 | |
---|
1394 | }, |
---|
1395 | |
---|
1396 | "transfer": function(parameters) { |
---|
1397 | |
---|
1398 | var srcL = getLayer(parseInt(parameters[0])); |
---|
1399 | var srcX = parseInt(parameters[1]); |
---|
1400 | var srcY = parseInt(parameters[2]); |
---|
1401 | var srcWidth = parseInt(parameters[3]); |
---|
1402 | var srcHeight = parseInt(parameters[4]); |
---|
1403 | var function_index = parseInt(parameters[5]); |
---|
1404 | var dstL = getLayer(parseInt(parameters[6])); |
---|
1405 | var dstX = parseInt(parameters[7]); |
---|
1406 | var dstY = parseInt(parameters[8]); |
---|
1407 | |
---|
1408 | /* SRC */ |
---|
1409 | if (function_index === 0x3) |
---|
1410 | display.put(srcL, srcX, srcY, srcWidth, srcHeight, |
---|
1411 | dstL, dstX, dstY); |
---|
1412 | |
---|
1413 | /* Anything else that isn't a NO-OP */ |
---|
1414 | else if (function_index !== 0x5) |
---|
1415 | display.transfer(srcL, srcX, srcY, srcWidth, srcHeight, |
---|
1416 | dstL, dstX, dstY, Guacamole.Client.DefaultTransferFunction[function_index]); |
---|
1417 | |
---|
1418 | }, |
---|
1419 | |
---|
1420 | "transform": function(parameters) { |
---|
1421 | |
---|
1422 | var layer = getLayer(parseInt(parameters[0])); |
---|
1423 | var a = parseFloat(parameters[1]); |
---|
1424 | var b = parseFloat(parameters[2]); |
---|
1425 | var c = parseFloat(parameters[3]); |
---|
1426 | var d = parseFloat(parameters[4]); |
---|
1427 | var e = parseFloat(parameters[5]); |
---|
1428 | var f = parseFloat(parameters[6]); |
---|
1429 | |
---|
1430 | display.transform(layer, a, b, c, d, e, f); |
---|
1431 | |
---|
1432 | }, |
---|
1433 | |
---|
1434 | "undefine" : function handleUndefine(parameters) { |
---|
1435 | |
---|
1436 | // Get object |
---|
1437 | var objectIndex = parseInt(parameters[0]); |
---|
1438 | var object = objects[objectIndex]; |
---|
1439 | |
---|
1440 | // Signal end of object definition |
---|
1441 | if (object && object.onundefine) |
---|
1442 | object.onundefine(); |
---|
1443 | |
---|
1444 | }, |
---|
1445 | |
---|
1446 | "video": function(parameters) { |
---|
1447 | |
---|
1448 | var stream_index = parseInt(parameters[0]); |
---|
1449 | var layer = getLayer(parseInt(parameters[1])); |
---|
1450 | var mimetype = parameters[2]; |
---|
1451 | |
---|
1452 | // Create stream |
---|
1453 | var stream = streams[stream_index] = |
---|
1454 | new Guacamole.InputStream(guac_client, stream_index); |
---|
1455 | |
---|
1456 | // Get player instance via callback |
---|
1457 | var videoPlayer = null; |
---|
1458 | if (guac_client.onvideo) |
---|
1459 | videoPlayer = guac_client.onvideo(stream, layer, mimetype); |
---|
1460 | |
---|
1461 | // If unsuccessful, try to use a default implementation |
---|
1462 | if (!videoPlayer) |
---|
1463 | videoPlayer = Guacamole.VideoPlayer.getInstance(stream, layer, mimetype); |
---|
1464 | |
---|
1465 | // If we have successfully retrieved an video player, send success response |
---|
1466 | if (videoPlayer) { |
---|
1467 | videoPlayers[stream_index] = videoPlayer; |
---|
1468 | guac_client.sendAck(stream_index, "OK", 0x0000); |
---|
1469 | } |
---|
1470 | |
---|
1471 | // Otherwise, mimetype must be unsupported |
---|
1472 | else |
---|
1473 | guac_client.sendAck(stream_index, "BAD TYPE", 0x030F); |
---|
1474 | |
---|
1475 | } |
---|
1476 | |
---|
1477 | }; |
---|
1478 | |
---|
1479 | tunnel.oninstruction = function(opcode, parameters) { |
---|
1480 | |
---|
1481 | var handler = instructionHandlers[opcode]; |
---|
1482 | if (handler) |
---|
1483 | handler(parameters); |
---|
1484 | |
---|
1485 | }; |
---|
1486 | |
---|
1487 | /** |
---|
1488 | * Sends a disconnect instruction to the server and closes the tunnel. |
---|
1489 | */ |
---|
1490 | this.disconnect = function() { |
---|
1491 | |
---|
1492 | // Only attempt disconnection not disconnected. |
---|
1493 | if (currentState != STATE_DISCONNECTED |
---|
1494 | && currentState != STATE_DISCONNECTING) { |
---|
1495 | |
---|
1496 | setState(STATE_DISCONNECTING); |
---|
1497 | |
---|
1498 | // Stop ping |
---|
1499 | if (pingInterval) |
---|
1500 | window.clearInterval(pingInterval); |
---|
1501 | |
---|
1502 | // Send disconnect message and disconnect |
---|
1503 | tunnel.sendMessage("disconnect"); |
---|
1504 | tunnel.disconnect(); |
---|
1505 | setState(STATE_DISCONNECTED); |
---|
1506 | |
---|
1507 | } |
---|
1508 | |
---|
1509 | }; |
---|
1510 | |
---|
1511 | /** |
---|
1512 | * Connects the underlying tunnel of this Guacamole.Client, passing the |
---|
1513 | * given arbitrary data to the tunnel during the connection process. |
---|
1514 | * |
---|
1515 | * @param data Arbitrary connection data to be sent to the underlying |
---|
1516 | * tunnel during the connection process. |
---|
1517 | * @throws {Guacamole.Status} If an error occurs during connection. |
---|
1518 | */ |
---|
1519 | this.connect = function(data) { |
---|
1520 | |
---|
1521 | setState(STATE_CONNECTING); |
---|
1522 | |
---|
1523 | try { |
---|
1524 | tunnel.connect(data); |
---|
1525 | } |
---|
1526 | catch (status) { |
---|
1527 | setState(STATE_IDLE); |
---|
1528 | throw status; |
---|
1529 | } |
---|
1530 | |
---|
1531 | // Ping every 5 seconds (ensure connection alive) |
---|
1532 | pingInterval = window.setInterval(function() { |
---|
1533 | tunnel.sendMessage("nop"); |
---|
1534 | }, 5000); |
---|
1535 | |
---|
1536 | setState(STATE_WAITING); |
---|
1537 | }; |
---|
1538 | |
---|
1539 | }; |
---|
1540 | |
---|
1541 | /** |
---|
1542 | * Map of all Guacamole binary raster operations to transfer functions. |
---|
1543 | * @private |
---|
1544 | */ |
---|
1545 | Guacamole.Client.DefaultTransferFunction = { |
---|
1546 | |
---|
1547 | /* BLACK */ |
---|
1548 | 0x0: function (src, dst) { |
---|
1549 | dst.red = dst.green = dst.blue = 0x00; |
---|
1550 | }, |
---|
1551 | |
---|
1552 | /* WHITE */ |
---|
1553 | 0xF: function (src, dst) { |
---|
1554 | dst.red = dst.green = dst.blue = 0xFF; |
---|
1555 | }, |
---|
1556 | |
---|
1557 | /* SRC */ |
---|
1558 | 0x3: function (src, dst) { |
---|
1559 | dst.red = src.red; |
---|
1560 | dst.green = src.green; |
---|
1561 | dst.blue = src.blue; |
---|
1562 | dst.alpha = src.alpha; |
---|
1563 | }, |
---|
1564 | |
---|
1565 | /* DEST (no-op) */ |
---|
1566 | 0x5: function (src, dst) { |
---|
1567 | // Do nothing |
---|
1568 | }, |
---|
1569 | |
---|
1570 | /* Invert SRC */ |
---|
1571 | 0xC: function (src, dst) { |
---|
1572 | dst.red = 0xFF & ~src.red; |
---|
1573 | dst.green = 0xFF & ~src.green; |
---|
1574 | dst.blue = 0xFF & ~src.blue; |
---|
1575 | dst.alpha = src.alpha; |
---|
1576 | }, |
---|
1577 | |
---|
1578 | /* Invert DEST */ |
---|
1579 | 0xA: function (src, dst) { |
---|
1580 | dst.red = 0xFF & ~dst.red; |
---|
1581 | dst.green = 0xFF & ~dst.green; |
---|
1582 | dst.blue = 0xFF & ~dst.blue; |
---|
1583 | }, |
---|
1584 | |
---|
1585 | /* AND */ |
---|
1586 | 0x1: function (src, dst) { |
---|
1587 | dst.red = ( src.red & dst.red); |
---|
1588 | dst.green = ( src.green & dst.green); |
---|
1589 | dst.blue = ( src.blue & dst.blue); |
---|
1590 | }, |
---|
1591 | |
---|
1592 | /* NAND */ |
---|
1593 | 0xE: function (src, dst) { |
---|
1594 | dst.red = 0xFF & ~( src.red & dst.red); |
---|
1595 | dst.green = 0xFF & ~( src.green & dst.green); |
---|
1596 | dst.blue = 0xFF & ~( src.blue & dst.blue); |
---|
1597 | }, |
---|
1598 | |
---|
1599 | /* OR */ |
---|
1600 | 0x7: function (src, dst) { |
---|
1601 | dst.red = ( src.red | dst.red); |
---|
1602 | dst.green = ( src.green | dst.green); |
---|
1603 | dst.blue = ( src.blue | dst.blue); |
---|
1604 | }, |
---|
1605 | |
---|
1606 | /* NOR */ |
---|
1607 | 0x8: function (src, dst) { |
---|
1608 | dst.red = 0xFF & ~( src.red | dst.red); |
---|
1609 | dst.green = 0xFF & ~( src.green | dst.green); |
---|
1610 | dst.blue = 0xFF & ~( src.blue | dst.blue); |
---|
1611 | }, |
---|
1612 | |
---|
1613 | /* XOR */ |
---|
1614 | 0x6: function (src, dst) { |
---|
1615 | dst.red = ( src.red ^ dst.red); |
---|
1616 | dst.green = ( src.green ^ dst.green); |
---|
1617 | dst.blue = ( src.blue ^ dst.blue); |
---|
1618 | }, |
---|
1619 | |
---|
1620 | /* XNOR */ |
---|
1621 | 0x9: function (src, dst) { |
---|
1622 | dst.red = 0xFF & ~( src.red ^ dst.red); |
---|
1623 | dst.green = 0xFF & ~( src.green ^ dst.green); |
---|
1624 | dst.blue = 0xFF & ~( src.blue ^ dst.blue); |
---|
1625 | }, |
---|
1626 | |
---|
1627 | /* AND inverted source */ |
---|
1628 | 0x4: function (src, dst) { |
---|
1629 | dst.red = 0xFF & (~src.red & dst.red); |
---|
1630 | dst.green = 0xFF & (~src.green & dst.green); |
---|
1631 | dst.blue = 0xFF & (~src.blue & dst.blue); |
---|
1632 | }, |
---|
1633 | |
---|
1634 | /* OR inverted source */ |
---|
1635 | 0xD: function (src, dst) { |
---|
1636 | dst.red = 0xFF & (~src.red | dst.red); |
---|
1637 | dst.green = 0xFF & (~src.green | dst.green); |
---|
1638 | dst.blue = 0xFF & (~src.blue | dst.blue); |
---|
1639 | }, |
---|
1640 | |
---|
1641 | /* AND inverted destination */ |
---|
1642 | 0x2: function (src, dst) { |
---|
1643 | dst.red = 0xFF & ( src.red & ~dst.red); |
---|
1644 | dst.green = 0xFF & ( src.green & ~dst.green); |
---|
1645 | dst.blue = 0xFF & ( src.blue & ~dst.blue); |
---|
1646 | }, |
---|
1647 | |
---|
1648 | /* OR inverted destination */ |
---|
1649 | 0xB: function (src, dst) { |
---|
1650 | dst.red = 0xFF & ( src.red | ~dst.red); |
---|
1651 | dst.green = 0xFF & ( src.green | ~dst.green); |
---|
1652 | dst.blue = 0xFF & ( src.blue | ~dst.blue); |
---|
1653 | } |
---|
1654 | |
---|
1655 | }; |
---|