1'use strict'; 2 3const RP_INIT_CONNECTION = 1; 4const RP_UPDATE_DISPLAY_MODE = 2; 5const RP_CLOSE_CONNECTION = 3; 6const RP_GET_SYSTEM_PALETTE = 4; 7const RP_GET_SYSTEM_PALETTE_RESULT = 5; 8 9const RP_CREATE_STATE = 20; 10const RP_DELETE_STATE = 21; 11const RP_ENABLE_SYNC_DRAWING = 22; 12const RP_DISABLE_SYNC_DRAWING = 23; 13const RP_INVALIDATE_RECT = 24; 14const RP_INVALIDATE_REGION = 25; 15 16const RP_SET_OFFSETS = 40; 17const RP_SET_HIGH_COLOR = 41; 18const RP_SET_LOW_COLOR = 42; 19const RP_SET_PEN_SIZE = 43; 20const RP_SET_STROKE_MODE = 44; 21const RP_SET_BLENDING_MODE = 45; 22const RP_SET_PATTERN = 46; 23const RP_SET_DRAWING_MODE = 47; 24const RP_SET_FONT = 48; 25const RP_SET_TRANSFORM = 49; 26 27const RP_CONSTRAIN_CLIPPING_REGION = 60; 28const RP_COPY_RECT_NO_CLIPPING = 61; 29const RP_INVERT_RECT = 62; 30const RP_DRAW_BITMAP = 63; 31const RP_DRAW_BITMAP_RECTS = 64; 32 33const RP_STROKE_ARC = 80; 34const RP_STROKE_BEZIER = 81; 35const RP_STROKE_ELLIPSE = 82; 36const RP_STROKE_POLYGON = 83; 37const RP_STROKE_RECT = 84; 38const RP_STROKE_ROUND_RECT = 85; 39const RP_STROKE_SHAPE = 86; 40const RP_STROKE_TRIANGLE = 87; 41const RP_STROKE_LINE = 88; 42const RP_STROKE_LINE_ARRAY = 89; 43 44const RP_FILL_ARC = 100; 45const RP_FILL_BEZIER = 101; 46const RP_FILL_ELLIPSE = 102; 47const RP_FILL_POLYGON = 103; 48const RP_FILL_RECT = 104; 49const RP_FILL_ROUND_RECT = 105; 50const RP_FILL_SHAPE = 106; 51const RP_FILL_TRIANGLE = 107; 52const RP_FILL_REGION = 108; 53 54const RP_FILL_ARC_GRADIENT = 120; 55const RP_FILL_BEZIER_GRADIENT = 121; 56const RP_FILL_ELLIPSE_GRADIENT = 122; 57const RP_FILL_POLYGON_GRADIENT = 123; 58const RP_FILL_RECT_GRADIENT = 124; 59const RP_FILL_ROUND_RECT_GRADIENT = 125; 60const RP_FILL_SHAPE_GRADIENT = 126; 61const RP_FILL_TRIANGLE_GRADIENT = 127; 62const RP_FILL_REGION_GRADIENT = 128; 63 64const RP_STROKE_POINT_COLOR = 140; 65const RP_STROKE_LINE_1PX_COLOR = 141; 66const RP_STROKE_RECT_1PX_COLOR = 142; 67 68const RP_FILL_RECT_COLOR = 160; 69const RP_FILL_REGION_COLOR_NO_CLIPPING = 161; 70 71const RP_DRAW_STRING = 180; 72const RP_DRAW_STRING_WITH_OFFSETS = 181; 73const RP_DRAW_STRING_RESULT = 182; 74const RP_STRING_WIDTH = 183; 75const RP_STRING_WIDTH_RESULT = 184; 76const RP_READ_BITMAP = 185; 77const RP_READ_BITMAP_RESULT = 186; 78 79const RP_SET_CURSOR = 200; 80const RP_SET_CURSOR_VISIBLE = 201; 81const RP_MOVE_CURSOR_TO = 202; 82 83const RP_MOUSE_MOVED = 220; 84const RP_MOUSE_DOWN = 221; 85const RP_MOUSE_UP = 222; 86const RP_MOUSE_WHEEL_CHANGED = 223; 87 88const RP_KEY_DOWN = 240; 89const RP_KEY_UP = 241; 90const RP_UNMAPPED_KEY_DOWN = 242; 91const RP_UNMAPPED_KEY_UP = 243; 92const RP_MODIFIERS_CHANGED = 244; 93 94 95// drawing_mode 96const B_OP_COPY = 0; 97const B_OP_OVER = 1; 98const B_OP_ERASE = 2; 99const B_OP_INVERT = 3; 100const B_OP_ADD = 4; 101const B_OP_SUBTRACT = 5; 102const B_OP_BLEND = 6; 103const B_OP_MIN = 7; 104const B_OP_MAX = 8; 105const B_OP_SELECT = 9; 106const B_OP_ALPHA = 10; 107 108 109// color_space 110const B_NO_COLOR_SPACE = 0x0000; 111const B_RGB32 = 0x0008; // BGR- 8:8:8:8 112const B_RGBA32 = 0x2008; // BGRA 8:8:8:8 113const B_RGB24 = 0x0003; // BGR 8:8:8 114const B_RGB16 = 0x0005; // BGR 5:6:5 115const B_RGB15 = 0x0010; // BGR- 5:5:5:1 116const B_RGBA15 = 0x2010; // BGRA 5:5:5:1 117const B_CMAP8 = 0x0004; // 256 color index table 118const B_GRAY8 = 0x0002; // 256 greyscale table 119const B_GRAY1 = 0x0001; // Each bit represents a single pixel 120const B_RGB32_BIG = 0x1008; // -RGB 8:8:8:8 121const B_RGBA32_BIG = 0x3008; // ARGB 8:8:8:8 122const B_RGB24_BIG = 0x1003; // RGB 8:8:8 123const B_RGB16_BIG = 0x1005; // RGB 5:6:5 124const B_RGB15_BIG = 0x1010; // -RGB 1:5:5:5 125const B_RGBA15_BIG = 0x3010; // ARGB 1:5:5:5 126 127const B_TRANSPARENT_MAGIC_CMAP8 = 0xff; 128const B_TRANSPARENT_MAGIC_RGBA15 = 0x39ce; 129const B_TRANSPARENT_MAGIC_RGBA15_BIG = 0xce39; 130const B_TRANSPARENT_MAGIC_RGBA32 = 0xff777477; 131const B_TRANSPARENT_MAGIC_RGBA32_BIG = 0x777477ff; 132 133 134// source_alpha 135const B_PIXEL_ALPHA = 0; 136const B_CONSTANT_ALPHA = 1; 137 138 139// alpha_function 140const B_ALPHA_OVERLAY = 0; 141const B_ALPHA_COMPOSITE = 1; 142 143 144// BGradient::Type 145const B_GRADIENT_TYPE_LINEAR = 0; 146const B_GRADIENT_TYPE_RADIAL = 1; 147const B_GRADIENT_TYPE_RADIAL_FOCUS = 2; 148const B_GRADIENT_TYPE_DIAMOND = 3; 149const B_GRADIENT_TYPE_CONIC = 4; 150const B_GRADIENT_TYPE_NONE = 5; 151 152 153// BShape ops 154const B_SHAPE_OP_MOVE_TO = 0x80000000; 155const B_SHAPE_OP_CLOSE = 0x40000000; 156const B_SHAPE_OP_BEZIER_TO = 0x20000000; 157const B_SHAPE_OP_LINE_TO = 0x10000000; 158const B_SHAPE_OP_SMALL_ARC_TO_CCW = 0x08000000; 159const B_SHAPE_OP_SMALL_ARC_TO_CW = 0x04000000; 160const B_SHAPE_OP_LARGE_ARC_TO_CCW = 0x02000000; 161const B_SHAPE_OP_LARGE_ARC_TO_CW = 0x01000000; 162 163 164// Line join_modes 165const B_ROUND_JOIN = 0; 166const B_MITER_JOIN = 1; 167const B_BEVEL_JOIN = 2; 168const B_BUTT_JOIN = 3; 169const B_SQUARE_JOIN = 4; 170 171 172// Line cap_modes 173const B_ROUND_CAP = B_ROUND_JOIN; 174const B_BUTT_CAP = B_BUTT_JOIN; 175const B_SQUARE_CAP = B_SQUARE_JOIN; 176 177 178const B_DEFAULT_MITER_LIMIT = 10; 179 180 181// Font spacing and face 182const B_FIXED_SPACING = 3; 183 184const B_ITALIC_FACE = 0x0001; 185const B_BOLD_FACE = 0x0020; 186 187 188// modifiers 189const B_SHIFT_KEY = 0x00000001; 190const B_COMMAND_KEY = 0x00000002; 191const B_CONTROL_KEY = 0x00000004; 192const B_CAPS_LOCK = 0x00000008; 193const B_SCROLL_LOCK = 0x00000010; 194const B_NUM_LOCK = 0x00000020; 195const B_OPTION_KEY = 0x00000040; 196const B_MENU_KEY = 0x00000080; 197const B_LEFT_SHIFT_KEY = 0x00000100; 198const B_RIGHT_SHIFT_KEY = 0x00000200; 199const B_LEFT_COMMAND_KEY = 0x00000400; 200const B_RIGHT_COMMAND_KEY = 0x00000800; 201const B_LEFT_CONTROL_KEY = 0x00001000; 202const B_RIGHT_CONTROL_KEY = 0x00002000; 203const B_LEFT_OPTION_KEY = 0x00004000; 204const B_RIGHT_OPTION_KEY = 0x00008000; 205 206 207 208var gSession; 209var gSystemPalette; 210 211 212function StreamingDataView(buffer, littleEndian, byteOffset, byteLength) 213{ 214 this.buffer = buffer; 215 this.dataView = new DataView(buffer.buffer, byteOffset, byteLength); 216 this.position = 0; 217 this.littleEndian = littleEndian; 218 this.textDecoder = new TextDecoder('utf-8'); 219 this.textEncoder = new TextEncoder(); 220} 221 222 223StreamingDataView.prototype.rewind = function() 224{ 225 this.position = 0; 226} 227 228 229StreamingDataView.prototype.readInt8 = function() 230{ 231 return this.dataView.getInt8(this.position++); 232} 233 234 235StreamingDataView.prototype.readUint8 = function() 236{ 237 return this.dataView.getUint8(this.position++); 238} 239 240 241StreamingDataView.prototype.readInt16 = function() 242{ 243 var result = this.dataView.getInt16(this.position, this.littleEndian); 244 this.position += 2; 245 return result; 246} 247 248 249StreamingDataView.prototype.readUint16 = function() 250{ 251 var result = this.dataView.getUint16(this.position, this.littleEndian); 252 this.position += 2; 253 return result; 254} 255 256 257StreamingDataView.prototype.readInt32 = function() 258{ 259 var result = this.dataView.getInt32(this.position, this.littleEndian); 260 this.position += 4; 261 return result; 262} 263 264 265StreamingDataView.prototype.readUint32 = function() 266{ 267 var result = this.dataView.getUint32(this.position, this.littleEndian); 268 this.position += 4; 269 return result; 270} 271 272 273StreamingDataView.prototype.readFloat32 = function() 274{ 275 var result = this.dataView.getFloat32(this.position, this.littleEndian); 276 this.position += 4; 277 return result; 278} 279 280 281StreamingDataView.prototype.readFloat64 = function() 282{ 283 var result = this.dataView.getFloat64(this.position, this.littleEndian); 284 this.position += 8; 285 return result; 286} 287 288 289StreamingDataView.prototype.readString = function(length) 290{ 291 var where = this.dataView.byteOffset + this.position; 292 var part = this.buffer.slice(where, where + length); 293 var result = this.textDecoder.decode(part); 294 this.position += length; 295 return result; 296} 297 298 299StreamingDataView.prototype.readInto = function(typedArray) 300{ 301 var where = this.dataView.byteOffset + this.position; 302 typedArray.set(this.buffer.slice(where, where + typedArray.byteLength)); 303 this.position += typedArray.byteLength; 304} 305 306 307StreamingDataView.prototype.writeInt8 = function(value) 308{ 309 this.dataView.setInt8(this.position++, value); 310} 311 312 313StreamingDataView.prototype.writeUint8 = function(value) 314{ 315 this.dataView.setUint8(this.position++, value); 316} 317 318 319StreamingDataView.prototype.writeInt16 = function(value) 320{ 321 this.dataView.setInt16(this.position, value, this.littleEndian); 322 this.position += 2; 323} 324 325 326StreamingDataView.prototype.writeUint16 = function(value) 327{ 328 this.dataView.setUint16(this.position, value, this.littleEndian); 329 this.position += 2; 330} 331 332 333StreamingDataView.prototype.writeInt32 = function(value) 334{ 335 this.dataView.setInt32(this.position, value, this.littleEndian); 336 this.position += 4; 337} 338 339 340StreamingDataView.prototype.writeUint32 = function(value) 341{ 342 this.dataView.setUint32(this.position, value, this.littleEndian); 343 this.position += 4; 344} 345 346 347StreamingDataView.prototype.writeFloat32 = function(value) 348{ 349 this.dataView.setFloat32(this.position, value, this.littleEndian); 350 this.position += 4; 351} 352 353 354StreamingDataView.prototype.writeFloat64 = function(value) 355{ 356 this.dataView.setFloat64(this.position, value, this.littleEndian); 357 this.position += 8; 358} 359 360 361StreamingDataView.prototype.writeString = function(string) 362{ 363 var encoded = this.textEncoder.encode(string); 364 this.writeUint32(encoded.length); 365 this.buffer.set(encoded, this.position); 366 this.position += encoded.length; 367} 368 369 370StreamingDataView.prototype.setUint32 = function(byteOffset, value) 371{ 372 this.dataView.setUint32(byteOffset, value, this.littleEndian); 373} 374 375 376StreamingDataView.prototype.pad = function(length) 377{ 378 this.buffer.fill(0, this.position, this.position + length); 379 this.position += length; 380} 381 382 383function RemotePoint(remoteMessage) 384{ 385 if (remoteMessage) { 386 this.readFrom(remoteMessage); 387 return; 388 } 389 390 this.x = 0; 391 this.y = 0; 392} 393 394 395RemotePoint.prototype.readFrom = function(remoteMessage) 396{ 397 this.x = remoteMessage.dataView.readFloat32(); 398 this.y = remoteMessage.dataView.readFloat32(); 399 return this; 400} 401 402 403RemotePoint.prototype.writeTo = function(remoteMessage) 404{ 405 remoteMessage.dataView.writeFloat32(this.x); 406 remoteMessage.dataView.writeFloat32(this.y); 407 return this; 408} 409 410 411function RemoteRect(remoteMessage) 412{ 413 if (remoteMessage) { 414 this.readFrom(remoteMessage); 415 return; 416 } 417 418 this.left = 0; 419 this.top = 0; 420 this.right = -1; 421 this.bottom = -1; 422} 423 424 425RemoteRect.prototype.readFrom = function(remoteMessage) 426{ 427 this.left = remoteMessage.dataView.readFloat32(); 428 this.top = remoteMessage.dataView.readFloat32(); 429 this.right = remoteMessage.dataView.readFloat32(); 430 this.bottom = remoteMessage.dataView.readFloat32(); 431 return this; 432} 433 434 435RemoteRect.prototype.width = function() 436{ 437 return this.right - this.left + 1; 438} 439 440 441RemoteRect.prototype.height = function() 442{ 443 return this.bottom - this.top + 1; 444} 445 446 447RemoteRect.prototype.integerWidth = function() 448{ 449 return Math.ceil(this.right - this.left); 450} 451 452 453RemoteRect.prototype.integerHeight = function() 454{ 455 return Math.ceil(this.bottom - this.top); 456} 457 458 459RemoteRect.prototype.centerX = function() 460{ 461 return this.left + this.width() / 2; 462} 463 464 465RemoteRect.prototype.centerY = function() 466{ 467 return this.top + this.height() / 2; 468} 469 470 471RemoteRect.prototype.apply = function(apply) 472{ 473 var left = Math.floor(this.left); 474 var top = Math.floor(this.top); 475 var right = Math.ceil(this.right); 476 var bottom = Math.ceil(this.bottom); 477 apply(left, top, right - left + 1, bottom - top + 1); 478} 479 480 481RemoteRect.prototype.applyAsEllipse = function(context, which) 482{ 483 context.beginPath(); 484 context.ellipse(this.centerX(), this.centerY(), this.width() / 2, 485 this.height() / 2, 0, Math.PI * 2, false); 486 which.call(context); 487} 488 489 490function RemoteColor(remoteMessage) 491{ 492 if (remoteMessage) { 493 this.readFrom(remoteMessage); 494 return; 495 } 496 497 this.red = 0; 498 this.green = 0; 499 this.blue = 0; 500 this.alpha = 0; 501} 502 503 504RemoteColor.prototype.readFrom = function(remoteMessage) 505{ 506 this.red = remoteMessage.dataView.readUint8(); 507 this.green = remoteMessage.dataView.readUint8(); 508 this.blue = remoteMessage.dataView.readUint8(); 509 this.alpha = remoteMessage.dataView.readUint8(); 510 return this; 511} 512 513 514RemoteColor.prototype.fromUint32 = function(value) 515{ 516 this.red = value & 0xff; 517 this.green = value >> 8 & 0xff; 518 this.blue = value >> 16 & 0xff; 519 this.alpha = value >> 24 & 0xff; 520 return this; 521} 522 523 524RemoteColor.prototype.toColor = function(unsetAlpha) 525{ 526 return 'rgba(' + this.red + ', ' + this.green + ', ' + this.blue + ', ' 527 + (unsetAlpha ? 1 : this.alpha / 255) + ')'; 528} 529 530 531RemoteColor.prototype.toUint32 = function(unsetAlpha) 532{ 533 return this.red | this.green << 8 | this.blue << 16 534 | (unsetAlpha ? 255 : this.alpha) << 24; 535} 536 537 538function RemoteFont(remoteMessage) 539{ 540 if (remoteMessage) { 541 this.readFrom(remoteMessage); 542 return; 543 } 544 545 this.direction = 0; 546 this.encoding = 0; 547 this.flags = 0; 548 this.spacing = 0; 549 this.shear = 0; 550 this.rotation = 0; 551 this.falseBoldWidth = 0; 552 this.size = 12; 553 this.face = 0; 554 this.family = 0; 555 this.style = 0; 556} 557 558 559RemoteFont.prototype.readFrom = function(remoteMessage) 560{ 561 this.direction = remoteMessage.dataView.readUint8(); 562 this.encoding = remoteMessage.dataView.readUint8(); 563 this.flags = remoteMessage.dataView.readUint32(); 564 this.spacing = remoteMessage.dataView.readUint8(); 565 this.shear = remoteMessage.dataView.readFloat32(); 566 this.rotation = remoteMessage.dataView.readFloat32(); 567 this.falseBoldWidth = remoteMessage.dataView.readFloat32(); 568 this.size = remoteMessage.dataView.readFloat32(); 569 this.face = remoteMessage.dataView.readUint16(); 570 this.family = remoteMessage.dataView.readUint16(); 571 this.style = remoteMessage.dataView.readUint16(); 572 return this; 573} 574 575 576function RemoteTransform(remoteMessage) 577{ 578 if (remoteMessage) { 579 this.readFrom(remoteMessage); 580 return; 581 } 582 583 this.setIdentity(); 584} 585 586 587RemoteTransform.prototype.readFrom = function(remoteMessage) 588{ 589 var isIdentity = remoteMessage.dataView.readUint8(); 590 if (isIdentity) { 591 this.setIdentity(); 592 return; 593 } 594 595 this.sx = remoteMessage.dataView.readFloat64(); 596 this.shy = remoteMessage.dataView.readFloat64(); 597 this.shx = remoteMessage.dataView.readFloat64(); 598 this.sy = remoteMessage.dataView.readFloat64(); 599 this.tx = remoteMessage.dataView.readFloat64(); 600 this.ty = remoteMessage.dataView.readFloat64(); 601 return this; 602} 603 604 605RemoteTransform.prototype.setIdentity = function() 606{ 607 this.sx = 1; 608 this.shy = 0; 609 this.shx = 0; 610 this.sy = 1; 611 this.tx = 0; 612 this.ty = 0; 613 return this; 614} 615 616 617RemoteTransform.prototype.isIdentity = function() 618{ 619 return this.sx == 1 && this.shy == 0 && this.shx == 0 && this.sy == 1 620 && this.tx == 0 && this.ty == 0; 621} 622 623 624RemoteTransform.prototype.apply = function(context) 625{ 626 context.transform(this.sx, this.shy, this.shx, this.sy, this.tx, 627 this.ty); 628} 629 630 631function RemoteBitmap(remoteMessage, unsetAlpha, colorSpace, flags) 632{ 633 if (remoteMessage) { 634 this.readFrom(remoteMessage, unsetAlpha, colorSpace, flags); 635 return; 636 } 637} 638 639 640RemoteBitmap.prototype.readFrom = function(remoteMessage, unsetAlpha, 641 colorSpace, flags) 642{ 643 this.width = remoteMessage.dataView.readUint32(); 644 this.height = remoteMessage.dataView.readUint32(); 645 this.bytesPerRow = remoteMessage.dataView.readUint32(); 646 647 if (colorSpace != undefined) { 648 this.colorSpace = colorSpace; 649 this.flags = flags; 650 } else { 651 this.colorSpace = remoteMessage.dataView.readUint32(); 652 this.flags = remoteMessage.dataView.readUint32(); 653 } 654 655 this.bitsLength = remoteMessage.dataView.readUint32(); 656 657 this.canvas = document.createElement('canvas'); 658 this.canvas.width = this.width; 659 this.canvas.height = this.height; 660 661 if (this.width == 0 || this.height == 0) 662 return; 663 664 var context = this.canvas.getContext('2d'); 665 var imageData = context.createImageData(this.width, this.height); 666 switch (this.colorSpace) { 667 case B_RGBA32: 668 remoteMessage.dataView.readInto(imageData.data); 669 var output = new Uint32Array(imageData.data.buffer); 670 671 for (var i = 0; i < imageData.data.length / 4; i++) { 672 output[i] = (output[i] & 0xff) << 16 | (output[i] >> 16 & 0xff) 673 | (output[i] & 0xff00ff00); 674 } 675 676 if (unsetAlpha) { 677 for (var i = 0; i < imageData.data.length / 4; i++) 678 output[i] |= 0xff000000; 679 } 680 681 break; 682 683 case B_RGB32: 684 remoteMessage.dataView.readInto(imageData.data); 685 var output = new Uint32Array(imageData.data.buffer); 686 687 for (var i = 0; i < imageData.data.length / 4; i++) { 688 output[i] = (output[i] & 0xff) << 16 | (output[i] >> 16 & 0xff) 689 | (output[i] & 0xff00) | 0xff000000; 690 691 if (!unsetAlpha && output[i] == B_TRANSPARENT_MAGIC_RGBA32) 692 output[i] &= 0x00ffffff; 693 } 694 break; 695 696 case B_RGB24: 697 var line = new Uint8Array(this.bytesPerRow); 698 var position = 0; 699 700 for (var y = 0; y < this.height; y++) { 701 remoteMessage.dataView.readInto(line); 702 703 for (var x = 0; x < this.width; x++) { 704 imageData.data[position++] = line[x * 3 + 2]; 705 imageData.data[position++] = line[x * 3 + 1]; 706 imageData.data[position++] = line[x * 3 + 0]; 707 imageData.data[position++] = 255; 708 } 709 } 710 711 break; 712 713 case B_RGB16: 714 var lineBuffer = new Uint8Array(this.bytesPerRow); 715 var line = new Uint16Array(lineBuffer.buffer); 716 var position = 0; 717 718 for (var y = 0; y < this.height; y++) { 719 remoteMessage.dataView.readInto(lineBuffer); 720 721 for (var x = 0; x < this.width; x++) { 722 imageData.data[position++] = (line[x] & 0xf800) >> 8; 723 imageData.data[position++] = (line[x] & 0x07e0) >> 3; 724 imageData.data[position++] = (line[x] & 0x001f) << 3; 725 imageData.data[position++] = 255; 726 } 727 } 728 729 break; 730 731 case B_CMAP8: 732 var line = new Uint8Array(this.bytesPerRow); 733 var output = new Uint32Array(imageData.data.buffer); 734 var position = 0; 735 736 for (var y = 0; y < this.height; y++) { 737 remoteMessage.dataView.readInto(line); 738 739 for (var x = 0; x < this.width; x++) 740 output[position++] = gSystemPalette[line[x]]; 741 } 742 743 break; 744 745 case B_GRAY8: 746 var source = new Uint8Array(this.bitsLength); 747 remoteMessage.dataView.readInto(source); 748 for (var i = 0; i < imageData.data.length / 4; i++) { 749 imageData.data[i * 4 + 0] = source[i]; 750 imageData.data[i * 4 + 1] = source[i]; 751 imageData.data[i * 4 + 2] = source[i]; 752 imageData.data[i * 4 + 3] = 255; 753 } 754 break; 755 756 case B_GRAY1: 757 var source = new Uint8Array(this.bitsLength); 758 remoteMessage.dataView.readInto(source); 759 for (var i = 0; i < imageData.data.length / 4; i++) { 760 var value = (source[Math.floor(i / 8)] >> i % 8) & 1 ? 255 : 0; 761 imageData.data[i * 4 + 0] = value; 762 imageData.data[i * 4 + 1] = value; 763 imageData.data[i * 4 + 2] = value; 764 imageData.data[i * 4 + 3] = 255; 765 } 766 break; 767 768 default: 769 console.warn('color space not implemented: ' + this.colorSpace); 770 break; 771 } 772 773 context.putImageData(imageData, 0, 0); 774 return this; 775} 776 777 778function RemotePattern(remoteMessage) 779{ 780 this.data = new Uint8Array(8); 781 782 if (remoteMessage) 783 this.readFrom(remoteMessage); 784 else 785 this.data.fill(255); 786} 787 788 789RemotePattern.staticCanvas = document.createElement('canvas'); 790RemotePattern.staticCanvas.width = RemotePattern.staticCanvas.height = 8; 791RemotePattern.staticContext = RemotePattern.staticCanvas.getContext('2d'); 792RemotePattern.staticImageData 793 = RemotePattern.staticContext.createImageData(8, 8); 794RemotePattern.staticPixels 795 = new Uint32Array(RemotePattern.staticImageData.data.buffer); 796 797 798RemotePattern.prototype.readFrom = function(remoteMessage) 799{ 800 remoteMessage.dataView.readInto(this.data); 801 return this; 802} 803 804 805RemotePattern.prototype.isSolid = function() 806{ 807 var common = this.data[0]; 808 return this.data.every(function(value) { return value == common; }); 809} 810 811 812RemotePattern.prototype.toPattern = function(context, lowColor, highColor) 813{ 814 for (var i = 0; i < this.data.length * 8; i++) { 815 RemotePattern.staticPixels[i] 816 = (this.data[i / 8 | 0] & 1 << 7 - i % 8) == 0 817 ? lowColor : highColor; 818 } 819 820 // Apparently supplying ImageData to createPattern fails in Chrome. 821 RemotePattern.staticContext.putImageData(RemotePattern.staticImageData, 0, 822 0); 823 return context.createPattern(RemotePattern.staticCanvas, 'repeat'); 824} 825 826 827function RemoteGradient(remoteMessage, context, unsetAlpha) 828{ 829 if (remoteMessage) { 830 this.readFrom(remoteMessage, context, unsetAlpha); 831 return; 832 } 833 834 this.gradient = '#00000000'; 835} 836 837 838RemoteGradient.prototype.readFrom = function(remoteMessage, context, unsetAlpha) 839{ 840 this.type = remoteMessage.dataView.readUint32(); 841 switch (this.type) { 842 case B_GRADIENT_TYPE_LINEAR: 843 var start = new RemotePoint(remoteMessage); 844 var end = new RemotePoint(remoteMessage); 845 846 this.gradient = context.createLinearGradient(start.x, start.y, 847 end.x, end.y); 848 break; 849 850 case B_GRADIENT_TYPE_RADIAL: 851 var center = new RemotePoint(remoteMessage); 852 var radius = remoteMessage.dataView.readFloat32(); 853 854 this.gradient = context.createRadialGradient(center.x, center.y, 0, 855 center.x, center.y, radius); 856 break; 857 858 default: 859 console.warn('gradient type not implemented: ' + this.type); 860 this.gradient = 'black'; 861 return this; 862 } 863 864 var stopCount = remoteMessage.dataView.readUint32(); 865 for (var i = 0; i < stopCount; i++) { 866 var color = remoteMessage.readColor(unsetAlpha); 867 var offset = remoteMessage.dataView.readFloat32() / 255; 868 this.gradient.addColorStop(offset, color); 869 } 870 871 return this; 872} 873 874 875function RemoteShape(remoteMessage) 876{ 877 if (remoteMessage) { 878 this.readFrom(remoteMessage); 879 return; 880 } 881 882 this.opCount = 0; 883 this.ops = []; 884 this.pointCount = 0; 885 this.points = []; 886} 887 888 889RemoteShape.prototype.readFrom = function(remoteMessage) 890{ 891 this.bounds = new RemoteRect(remoteMessage); 892 893 this.opCount = remoteMessage.dataView.readUint32(); 894 this.ops = new Array(this.opCount); 895 for (var i = 0; i < this.opCount; i++) 896 this.ops[i] = remoteMessage.dataView.readUint32(); 897 898 this.pointCount = remoteMessage.dataView.readUint32(); 899 this.points = new Array(this.pointCount); 900 for (var i = 0; i < this.pointCount; i++) 901 this.points[i] = new RemotePoint(remoteMessage); 902 903 return this; 904} 905 906 907RemoteShape.prototype.play = function(context) 908{ 909 var pointIndex = 0; 910 for (var i = 0; i < this.opCount; i++) { 911 var op = this.ops[i] & 0xff000000; 912 var count = this.ops[i] & 0x00ffffff; 913 914 if (op & B_SHAPE_OP_MOVE_TO) { 915 var point = this.points[pointIndex++]; 916 context.moveTo(point.x, point.y); 917 } 918 919 if (op & B_SHAPE_OP_LINE_TO) { 920 for (var j = 0; j < count; j++) { 921 var point = this.points[pointIndex++]; 922 context.lineTo(point.x, point.y); 923 } 924 } 925 926 if (op & B_SHAPE_OP_BEZIER_TO) { 927 for (var j = 0; j < count / 3; j++) { 928 var control1 = this.points[pointIndex++]; 929 var control2 = this.points[pointIndex++]; 930 var to = this.points[pointIndex++]; 931 context.bezierCurveTo(control1.x, control1.y, control2.x, 932 control2.y, to.x, to.y); 933 } 934 } 935 936 if (op & (B_SHAPE_OP_LARGE_ARC_TO_CW | B_SHAPE_OP_LARGE_ARC_TO_CCW 937 | B_SHAPE_OP_SMALL_ARC_TO_CW | B_SHAPE_OP_SMALL_ARC_TO_CCW)) { 938 939 console.warn('shape op arc to not implemented'); 940 for (var j = 0; j < count / 3; j++) 941 pointIndex++; 942 } 943 944 if (op & B_SHAPE_OP_CLOSE) 945 context.closePath(); 946 } 947} 948 949 950function RemoteMessage(socket) 951{ 952 this.socket = socket; 953} 954 955 956RemoteMessage.staticRemoteColor = new RemoteColor(); 957 958 959RemoteMessage.prototype.allocate = function(bufferSize) 960{ 961 this.buffer = new Uint8Array(bufferSize); 962 this.dataView = new StreamingDataView(this.buffer, true); 963} 964 965 966RemoteMessage.prototype.ensureBufferSize = function(bufferSize) 967{ 968 if (this.buffer.byteLength < bufferSize) 969 this.allocate(bufferSize); 970} 971 972 973RemoteMessage.prototype.attach = function(buffer, byteOffset) 974{ 975 var bytesLeft = buffer.byteLength - byteOffset; 976 if (bytesLeft < 6) 977 return false; 978 979 this.buffer = buffer; 980 this.dataView = new StreamingDataView(this.buffer, true, byteOffset); 981 this.messageCode = this.dataView.readUint16(); 982 this.messageSize = this.dataView.readUint32(); 983 if (this.messageSize < 6) 984 throw false; 985 986 return this.messageSize <= bytesLeft; 987} 988 989 990RemoteMessage.prototype.code = function() 991{ 992 return this.messageCode; 993} 994 995 996RemoteMessage.prototype.size = function() 997{ 998 return this.messageSize; 999} 1000 1001 1002RemoteMessage.prototype.start = function(code) 1003{ 1004 this.dataView.rewind(); 1005 this.dataView.writeUint16(code); 1006 this.dataView.writeUint32(0); 1007 // Placeholder for size field. 1008} 1009 1010 1011RemoteMessage.prototype.flush = function() 1012{ 1013 this.dataView.setUint32(2, this.dataView.position); 1014 this.socket.send(this.buffer.slice(0, this.dataView.position)); 1015} 1016 1017 1018RemoteMessage.prototype.readColor = function(unsetAlpha) 1019{ 1020 return RemoteMessage.staticRemoteColor.readFrom(this).toColor(unsetAlpha); 1021} 1022 1023 1024function RemoteState(session, token) 1025{ 1026 this.session = session; 1027 this.token = token; 1028 1029 this.lowColor = new RemoteColor().fromUint32(0xffffffff); 1030 this.highColor = new RemoteColor().fromUint32(0xff000000); 1031 1032 this.penSize = 1.0; 1033 this.lineCap = 'butt'; 1034 this.lineJoin = 'miter'; 1035 this.miterLimit = B_DEFAULT_MITER_LIMIT; 1036 this.drawingMode = 'source-over'; 1037 1038 this.pattern = new RemotePattern(); 1039 this.font = new RemoteFont(); 1040 this.transform = new RemoteTransform(); 1041} 1042 1043 1044RemoteState.prototype.applyContext = function() 1045{ 1046 var context = this.session.context; 1047 if (!this.invalidated && context.currentToken == this.token) 1048 return; 1049 1050 this.session.removeClipping(); 1051 1052 if (this.blendModesEnabled && this.constantAlpha) 1053 context.globalAlpha = this.highColor.alpha / 255; 1054 else 1055 context.globalAlpha = 1; 1056 1057 var style; 1058 if (this.pattern.isSolid()) { 1059 style = this.pattern.data[0] == 0 ? this.lowColor : this.highColor; 1060 if (this.invert) 1061 style = style == this.lowColor ? 'transparent' : 'white'; 1062 else 1063 style = style.toColor(this.unsetAlpha); 1064 } else { 1065 style = this.pattern.toPattern(context, 1066 this.invert ? 0x00000000 : this.lowColor.toUint32(this.unsetAlpha), 1067 this.invert 1068 ? 0xffffffff : this.highColor.toUint32(this.unsetAlpha)); 1069 } 1070 1071 context.fillStyle = context.strokeStyle = style; 1072 1073 context.font = (this.font.face & B_ITALIC_FACE ? "italic " : "") 1074 + (this.font.face & B_BOLD_FACE ? "bold " : "") 1075 + this.font.size + 'px ' 1076 + (this.font.spacing==B_FIXED_SPACING ? 'monospace' : 'Helvetica'); 1077 context.globalCompositeOperation = this.drawingMode; 1078 context.lineWidth = this.penSize; 1079 context.lineCap = this.lineCap; 1080 context.lineJoin = this.lineJoin; 1081 context.miterLimit = this.miterLimit; 1082 1083 context.resetTransform(); 1084 1085 this.session.applyClipping(this.clipRects); 1086 1087 if (!this.transform.isIdentity()) { 1088 context.translate(this.xOffset, this.yOffset); 1089 this.transform.apply(context); 1090 context.translate(-this.xOffset, -this.yOffset); 1091 } 1092 1093 context.currentToken = this.token; 1094 this.invalidated = false; 1095} 1096 1097 1098RemoteState.prototype.prepareForRect = function() 1099{ 1100 this.session.context.lineJoin = 'miter'; 1101 this.session.context.miterLimit = 10; 1102} 1103 1104 1105RemoteState.prototype.messageReceived = function(remoteMessage, reply) 1106{ 1107 var context = this.session.context; 1108 1109 switch (remoteMessage.code()) { 1110 case RP_ENABLE_SYNC_DRAWING: 1111 case RP_DISABLE_SYNC_DRAWING: 1112 console.warn('sync drawing en-/disable not implemented'); 1113 break; 1114 1115 case RP_SET_LOW_COLOR: 1116 this.lowColor.readFrom(remoteMessage); 1117 this.invalidated = true; 1118 break; 1119 1120 case RP_SET_HIGH_COLOR: 1121 this.highColor.readFrom(remoteMessage); 1122 this.invalidated = true; 1123 break; 1124 1125 case RP_SET_OFFSETS: 1126 this.xOffset = remoteMessage.dataView.readInt32(); 1127 this.yOffset = remoteMessage.dataView.readInt32(); 1128 this.invalidated = true; 1129 break; 1130 1131 case RP_SET_FONT: 1132 this.font = new RemoteFont(remoteMessage); 1133 this.invalidated = true; 1134 break; 1135 1136 case RP_SET_TRANSFORM: 1137 this.transform = new RemoteTransform(remoteMessage); 1138 this.invalidated = true; 1139 break; 1140 1141 case RP_SET_PATTERN: 1142 this.pattern = new RemotePattern(remoteMessage); 1143 this.invalidated = true; 1144 break; 1145 1146 case RP_SET_PEN_SIZE: 1147 this.penSize = remoteMessage.dataView.readFloat32(); 1148 this.invalidated = true; 1149 break; 1150 1151 case RP_SET_STROKE_MODE: 1152 switch (remoteMessage.dataView.readUint32()) { 1153 case B_ROUND_CAP: 1154 this.lineCap = 'round'; 1155 break; 1156 1157 case B_BUTT_CAP: 1158 this.lineCap = 'butt'; 1159 break; 1160 1161 case B_SQUARE_CAP: 1162 this.lineCap = 'square'; 1163 break; 1164 } 1165 1166 var lineJoin = remoteMessage.dataView.readUint32(); 1167 switch (lineJoin) { 1168 case B_ROUND_JOIN: 1169 this.lineJoin = 'round'; 1170 break; 1171 1172 case B_MITER_JOIN: 1173 this.lineJoin = 'miter'; 1174 break; 1175 1176 case B_BEVEL_JOIN: 1177 this.lineJoin = 'bevel'; 1178 break; 1179 1180 default: 1181 console.warn('line join not implemented: ' + join); 1182 break; 1183 } 1184 1185 this.miterLimit = remoteMessage.dataView.readFloat32(); 1186 this.invalidated = true; 1187 break; 1188 1189 case RP_SET_BLENDING_MODE: 1190 var sourceAlpha = remoteMessage.dataView.readUint32(); 1191 this.constantAlpha = sourceAlpha == B_CONSTANT_ALPHA; 1192 if (this.blendModesEnabled) 1193 this.unsetAlpha = this.constantAlpha; 1194 1195 var alphaFunction = remoteMessage.dataView.readUint32(); 1196 if (alphaFunction != B_ALPHA_OVERLAY) 1197 console.warn('alpha function not supported: ' + alphaFunction); 1198 1199 this.invalidated = true; 1200 break; 1201 1202 case RP_SET_DRAWING_MODE: 1203 var drawingMode = remoteMessage.dataView.readUint32(); 1204 1205 this.unsetAlpha = false; 1206 this.blendModesEnabled = false; 1207 this.invert = false; 1208 1209 switch (drawingMode) { 1210 case B_OP_COPY: 1211 this.unsetAlpha = true; 1212 this.drawingMode = 'source-over'; 1213 break; 1214 1215 case B_OP_OVER: 1216 this.drawingMode = 'source-over'; 1217 break; 1218 1219 case B_OP_ALPHA: 1220 this.blendModesEnabled = true; 1221 this.unsetAlpha = this.constantAlpha; 1222 this.drawingMode = 'source-over'; 1223 break; 1224 1225 case B_OP_BLEND: 1226 this.drawingMode = 'lighter'; 1227 break; 1228 1229 case B_OP_MIN: 1230 this.drawingMode = 'darken'; 1231 break; 1232 1233 case B_OP_MAX: 1234 this.drawingMode = 'ligthen'; 1235 break; 1236 1237 case B_OP_INVERT: 1238 this.drawingMode = 'difference'; 1239 this.invert = true; 1240 break; 1241 1242 case B_OP_ADD: 1243 this.drawingMode = 'lighter'; 1244 break; 1245 1246/* 1247 case B_OP_ERASE: 1248 this.drawingMode = 'destination-out'; 1249 break; 1250 1251 case B_OP_SUBTRACT: 1252 this.drawingMode = 'difference'; 1253 break; 1254*/ 1255 1256 default: 1257 console.warn('drawing mode not implemented: ' 1258 + drawingMode); 1259 this.drawingMode = 'source-over'; 1260 break; 1261 } 1262 1263 this.invalidated = true; 1264 break; 1265 1266 case RP_CONSTRAIN_CLIPPING_REGION: 1267 var rectCount = remoteMessage.dataView.readUint32(); 1268 this.clipRects = new Array(rectCount); 1269 for (var i = 0; i < rectCount; i++) 1270 this.clipRects[i] = new RemoteRect(remoteMessage); 1271 1272 this.invalidated = true; 1273 break; 1274 1275 case RP_INVERT_RECT: 1276 this.applyContext(); 1277 1278 var rect = new RemoteRect(remoteMessage); 1279 1280 context.save(); 1281 context.globalCompositeOperation = 'difference'; 1282 context.fillStyle = 'white'; 1283 this.prepareForRect(); 1284 1285 rect.apply(context.fillRect.bind(context)); 1286 1287 context.restore(); 1288 break; 1289 1290 case RP_DRAW_BITMAP: 1291 this.applyContext(); 1292 1293 var bitmapRect = new RemoteRect(remoteMessage); 1294 var viewRect = new RemoteRect(remoteMessage); 1295 var options = remoteMessage.dataView.readUint32(); 1296 // TODO: Implement options. 1297 1298 if (options != 0) 1299 console.warn('bitmap options not supported: ' + options); 1300 1301 var bitmap = new RemoteBitmap(remoteMessage, this.unsetAlpha); 1302 context.drawImage(bitmap.canvas, bitmapRect.left, bitmapRect.top, 1303 bitmapRect.width(), bitmapRect.height(), viewRect.left, 1304 viewRect.top, viewRect.width(), viewRect.height()); 1305 break; 1306 1307 case RP_DRAW_BITMAP_RECTS: 1308 this.applyContext(); 1309 1310 var options = remoteMessage.dataView.readUint32(); 1311 // TODO: Implement options. 1312 var colorSpace = remoteMessage.dataView.readUint32(); 1313 var flags = remoteMessage.dataView.readUint32(); 1314 1315 if (options != 0) 1316 console.warn('bitmap options not supported: ' + options); 1317 1318 var rectCount = remoteMessage.dataView.readUint32(); 1319 for (var i = 0; i < rectCount; i++) { 1320 var rect = new RemoteRect(remoteMessage); 1321 var bitmap = new RemoteBitmap(remoteMessage, this.unsetAlpha, 1322 colorSpace, flags); 1323 1324 context.drawImage(bitmap.canvas, 0, 0, bitmap.width, 1325 bitmap.height, rect.left, rect.top, rect.width(), 1326 rect.height()); 1327 } 1328 break; 1329 1330 case RP_DRAW_STRING: 1331 this.applyContext(); 1332 1333 var where = new RemotePoint(remoteMessage); 1334 var length = remoteMessage.dataView.readUint32(); 1335 var string = remoteMessage.dataView.readString(length); 1336 1337 context.save(); 1338 context.fillStyle = this.highColor.toColor(this.unsetAlpha); 1339 context.fillText(string, where.x, where.y); 1340 1341 var textMetric = context.measureText(string); 1342 where.x += textMetric.width; 1343 1344 context.restore(); 1345 1346 reply.start(RP_DRAW_STRING_RESULT); 1347 reply.dataView.writeInt32(this.token); 1348 where.writeTo(reply); 1349 reply.flush(); 1350 break; 1351 1352 case RP_DRAW_STRING_WITH_OFFSETS: 1353 this.applyContext(); 1354 1355 var length = remoteMessage.dataView.readUint32(); 1356 var string = remoteMessage.dataView.readString(length); 1357 1358 context.save(); 1359 context.fillStyle = this.highColor.toColor(this.unsetAlpha); 1360 1361 var where; 1362 for (var i = 0; i < string.length; i++) { 1363 where = new RemotePoint(remoteMessage); 1364 context.fillText(string[i], where.x, where.y); 1365 } 1366 1367 var textMetric = context.measureText(string[string.length - 1]); 1368 where.x += textMetric.width; 1369 1370 context.restore(); 1371 1372 reply.start(RP_DRAW_STRING_RESULT); 1373 reply.dataView.writeInt32(this.token); 1374 where.writeTo(reply); 1375 reply.flush(); 1376 break; 1377 1378 case RP_STRING_WIDTH: 1379 this.applyContext(); 1380 1381 var length = remoteMessage.dataView.readUint32(); 1382 var string = remoteMessage.dataView.readString(length); 1383 var textMetric = context.measureText(string); 1384 1385 reply.start(RP_STRING_WIDTH_RESULT); 1386 reply.dataView.writeInt32(this.token); 1387 where.writeFloat32(textMetric.width); 1388 reply.flush(); 1389 break; 1390 1391 case RP_STROKE_ARC: 1392 case RP_FILL_ARC: 1393 this.applyContext(); 1394 1395 var rect = new RemoteRect(remoteMessage); 1396 var startAngle 1397 = remoteMessage.dataView.readFloat32() * Math.PI / 180; 1398 var invertStart = Math.PI * 2 - startAngle; 1399 startAngle += Math.PI / 2; 1400 1401 var span = remoteMessage.dataView.readFloat32() * Math.PI / 180; 1402 var centerX = Math.round(rect.centerX()); 1403 var centerY = Math.round(rect.centerY()); 1404 var radius = rect.width() / 2; 1405 var maxSpan 1406 = remoteMessage.code() != RP_STROKE_ARC ? Math.PI / 2 : span; 1407 1408 var arcStep = function(max) { 1409 max = Math.min(max, span); 1410 1411 context.beginPath(); 1412 context.arc(centerX, centerY, radius, invertStart, 1413 invertStart - max, true); 1414 1415 switch (remoteMessage.code()) { 1416 case RP_STROKE_ARC: 1417 context.stroke(); 1418 break; 1419 1420 case RP_FILL_ARC: 1421 context.moveTo(centerX, centerY); 1422 var endAngle = startAngle + max; 1423 context.lineTo( 1424 centerX + radius * Math.sin(startAngle), 1425 centerY + radius * Math.cos(startAngle)); 1426 context.lineTo( 1427 centerX + radius * Math.sin(endAngle), 1428 centerY + radius * Math.cos(endAngle)); 1429 context.fill(); 1430 break; 1431 } 1432 1433 startAngle += max; 1434 invertStart -= max; 1435 span -= max; 1436 }; 1437 1438 while (span > 0) 1439 arcStep(maxSpan); 1440 1441 break; 1442 1443 case RP_STROKE_RECT: 1444 case RP_STROKE_ELLIPSE: 1445 case RP_FILL_RECT: 1446 case RP_FILL_ELLIPSE: 1447 this.applyContext(); 1448 1449 context.save(); 1450 this.prepareForRect(); 1451 1452 var rect = new RemoteRect(remoteMessage); 1453 1454 switch (remoteMessage.code()) { 1455 case RP_STROKE_RECT: 1456 rect.apply(context.strokeRect.bind(context)); 1457 break; 1458 case RP_STROKE_ELLIPSE: 1459 rect.applyAsEllipse(context, context.stroke); 1460 break; 1461 case RP_FILL_RECT: 1462 rect.apply(context.fillRect.bind(context)); 1463 break; 1464 case RP_FILL_ELLIPSE: 1465 rect.applyAsEllipse(context, context.fill); 1466 break; 1467 } 1468 1469 context.restore(); 1470 break; 1471 1472 case RP_STROKE_ROUND_RECT: 1473 case RP_FILL_ROUND_RECT: 1474 case RP_FILL_ROUND_RECT_GRADIENT: 1475 this.applyContext(); 1476 1477 context.save(); 1478 this.prepareForRect(); 1479 1480 var rect = new RemoteRect(remoteMessage); 1481 var xRadius = remoteMessage.dataView.readFloat32(); 1482 var yRadius = remoteMessage.dataView.readFloat32(); 1483 1484 if (remoteMessage.code() == RP_FILL_ROUND_RECT_GRADIENT) { 1485 context.save(); 1486 var gradient = new RemoteGradient(remoteMessage, context, 1487 this.unsetAlpha); 1488 context.fillStyle = gradient.gradient; 1489 } 1490 1491 console.warn('round rects not implemented, falling back to rect'); 1492 if (remoteMessage.code() == RP_STROKE_ROUND_RECT) 1493 rect.apply(context.strokeRect.bind(context)); 1494 else 1495 rect.apply(context.fillRect.bind(context)); 1496 1497 if (remoteMessage.code() == RP_FILL_ROUND_RECT_GRADIENT) 1498 context.restore(); 1499 1500 context.restore(); 1501 break; 1502 1503 case RP_STROKE_LINE: 1504 this.applyContext(); 1505 1506 var from = new RemotePoint(remoteMessage); 1507 var to = new RemotePoint(remoteMessage); 1508 1509 context.beginPath(); 1510 context.moveTo(from.x, from.y); 1511 context.lineTo(to.x, to.y); 1512 context.stroke(); 1513 break; 1514 1515 case RP_STROKE_LINE_ARRAY: 1516 this.applyContext(); 1517 1518 context.save(); 1519 context.lineCap = 'square'; 1520 1521 var numLines = remoteMessage.dataView.readUint32(); 1522 for (var i = 0; i < numLines; i++) { 1523 var from = new RemotePoint(remoteMessage); 1524 var to = new RemotePoint(remoteMessage); 1525 context.strokeStyle = remoteMessage.readColor(this.unsetAlpha); 1526 context.beginPath(); 1527 context.moveTo(from.x + 0.5, from.y + 0.5); 1528 context.lineTo(to.x + 0.5, to.y + 0.5); 1529 context.stroke(); 1530 } 1531 1532 context.restore(); 1533 break; 1534 1535 case RP_STROKE_POINT_COLOR: 1536 this.applyContext(); 1537 1538 var point = new RemotePoint(remoteMessage); 1539 1540 context.save(); 1541 context.fillStyle = remoteMessage.readColor(this.unsetAlpha); 1542 1543 context.fillRect(point.x, point.y, 1, 1); 1544 context.restore(); 1545 break; 1546 1547 case RP_STROKE_LINE_1PX_COLOR: 1548 this.applyContext(); 1549 1550 var from = new RemotePoint(remoteMessage); 1551 var to = new RemotePoint(remoteMessage); 1552 1553 context.save(); 1554 context.strokeStyle = remoteMessage.readColor(this.unsetAlpha); 1555 context.lineWidth = 1; 1556 context.lineCap = 'square'; 1557 1558 context.beginPath(); 1559 context.moveTo(from.x + 0.5, from.y + 0.5); 1560 context.lineTo(to.x + 0.5, to.y + 0.5); 1561 context.stroke(); 1562 1563 context.restore(); 1564 break; 1565 1566 case RP_STROKE_RECT_1PX_COLOR: 1567 this.applyContext(); 1568 1569 var rect = new RemoteRect(remoteMessage); 1570 1571 context.save(); 1572 this.prepareForRect(); 1573 1574 context.strokeStyle = remoteMessage.readColor(this.unsetAlpha); 1575 context.lineWidth = 1; 1576 1577 rect.apply(context.strokeRect.bind(context)); 1578 1579 context.restore(); 1580 break; 1581 1582 case RP_STROKE_SHAPE: 1583 case RP_FILL_SHAPE: 1584 case RP_FILL_SHAPE_GRADIENT: 1585 this.applyContext(); 1586 1587 var shape = new RemoteShape(remoteMessage); 1588 var offset = new RemotePoint(remoteMessage); 1589 var scale = remoteMessage.dataView.readFloat32(); 1590 1591 context.save(); 1592 if (remoteMessage.code() == RP_FILL_SHAPE_GRADIENT) { 1593 var gradient = new RemoteGradient(remoteMessage, context, 1594 this.unsetAlpha); 1595 context.fillStyle = gradient.gradient; 1596 } 1597 1598 context.translate(offset.x + 0.5, offset.y + 0.5); 1599 context.scale(scale, scale); 1600 1601 context.beginPath(); 1602 1603 shape.play(context); 1604 1605 if (remoteMessage.code() == RP_STROKE_SHAPE) 1606 context.stroke(); 1607 else 1608 context.fill(); 1609 1610 context.restore(); 1611 break; 1612 1613 case RP_STROKE_TRIANGLE: 1614 case RP_FILL_TRIANGLE: 1615 case RP_FILL_TRIANGLE_GRADIENT: 1616 this.applyContext(); 1617 1618 if (remoteMessage.code() == RP_FILL_TRIANGLE_GRADIENT) 1619 context.save(); 1620 1621 context.beginPath(); 1622 var point = new RemotePoint(remoteMessage); 1623 context.moveTo(point.x + 0.5, point.y + 0.5); 1624 1625 for (var i = 0; i < 2; i++) { 1626 point = new RemotePoint(remoteMessage); 1627 context.lineTo(point.x + 0.5, point.y + 0.5); 1628 } 1629 1630 if (remoteMessage.code() == RP_FILL_TRIANGLE_GRADIENT) { 1631 var unusedBounds = new RemoteRect(remoteMessage); 1632 var gradient = new RemoteGradient(remoteMessage, context, 1633 this.unsetAlpha); 1634 context.fillStyle = gradient.gradient; 1635 } 1636 1637 switch (remoteMessage.code()) { 1638 case RP_STROKE_TRIANGLE: 1639 context.closePath(); 1640 context.stroke(); 1641 break; 1642 1643 case RP_FILL_TRIANGLE: 1644 context.fill(); 1645 break; 1646 1647 case RP_FILL_TRIANGLE_GRADIENT: 1648 context.fill(); 1649 context.restore(); 1650 break; 1651 } 1652 1653 break; 1654 1655 case RP_FILL_RECT_COLOR: 1656 this.applyContext(); 1657 1658 var rect = new RemoteRect(remoteMessage); 1659 1660 context.save(); 1661 this.prepareForRect(); 1662 context.fillStyle = remoteMessage.readColor(this.unsetAlpha); 1663 1664 rect.apply(context.fillRect.bind(context)); 1665 1666 context.restore(); 1667 break; 1668 1669 case RP_FILL_RECT_GRADIENT: 1670 case RP_FILL_ELLIPSE_GRADIENT: 1671 this.applyContext(); 1672 1673 var rect = new RemoteRect(remoteMessage); 1674 1675 context.save(); 1676 this.prepareForRect(); 1677 1678 var gradient = new RemoteGradient(remoteMessage, context, 1679 this.unsetAlpha); 1680 context.fillStyle = gradient.gradient; 1681 1682 if (remoteMessage.code() == RP_FILL_RECT_GRADIENT) 1683 rect.apply(context.fillRect.bind(context)); 1684 else 1685 rect.applyAsEllipse(context, context.fill); 1686 1687 context.restore(); 1688 break; 1689 1690 case RP_FILL_REGION: 1691 case RP_FILL_REGION_GRADIENT: 1692 this.applyContext(); 1693 1694 var rectCount = remoteMessage.dataView.readUint32(); 1695 var rects = new Array(rectCount); 1696 for (var i = 0; i < rectCount; i++) 1697 rects[i] = new RemoteRect(remoteMessage); 1698 1699 if (remoteMessage.code() == RP_FILL_REGION_GRADIENT) { 1700 context.save(); 1701 var gradient = new RemoteGradient(remoteMessage, context, 1702 this.unsetAlpha); 1703 context.fillStyle = gradient.gradient; 1704 } 1705 1706 for (var i = 0; i < rectCount; i++) 1707 rects[i].apply(context.fillRect.bind(context)); 1708 1709 if (remoteMessage.code() == RP_FILL_REGION_GRADIENT) 1710 context.restore(); 1711 1712 break; 1713 1714 case RP_READ_BITMAP: 1715 var bounds = new RemoteRect(remoteMessage); 1716 var drawCursor = remoteMessage.dataView.readUint8(); 1717 // TODO: Support the drawCursor flag. 1718 1719 if (drawCursor) 1720 console.warn('draw cursor in read bitmap not supported'); 1721 1722 var width = bounds.integerWidth() + 1; 1723 var height = bounds.integerHeight() + 1; 1724 var bytesPerPixel = 3; 1725 var bytesPerRow = (width * bytesPerPixel + 3) & ~7; 1726 var padding = bytesPerRow - width * bytesPerPixel; 1727 var bitsLength = height * bytesPerRow; 1728 1729 reply.ensureBufferSize(bitsLength + 1024); 1730 1731 reply.start(RP_READ_BITMAP_RESULT); 1732 reply.dataView.writeInt32(this.token); 1733 1734 reply.dataView.writeInt32(width); 1735 reply.dataView.writeInt32(height); 1736 reply.dataView.writeInt32(bytesPerRow); 1737 reply.dataView.writeUint32(B_RGB24); 1738 reply.dataView.writeUint32(0); // Flags 1739 reply.dataView.writeUint32(bitsLength); 1740 1741 var position = 0; 1742 var imageData 1743 = context.getImageData(bounds.left, bounds.top, width, height); 1744 for (var y = 0; y < height; y++) { 1745 for (var x = 0; x < width; x++, position += 4) { 1746 reply.dataView.writeUint8(imageData.data[position + 2]); 1747 reply.dataView.writeUint8(imageData.data[position + 1]); 1748 reply.dataView.writeUint8(imageData.data[position + 0]); 1749 } 1750 1751 reply.dataView.pad(padding); 1752 } 1753 1754 reply.flush(); 1755 break; 1756 1757 default: 1758 console.warn('unhandled message: code: ' + remoteMessage.code() 1759 + '; size: ' + remoteMessage.size()); 1760 break; 1761 } 1762} 1763 1764 1765function RemoteDesktopSession(targetElement, width, height, targetAddress, 1766 disconnectCallback) 1767{ 1768 this.websocket = new WebSocket(targetAddress, 'binary'); 1769 this.websocket.binaryType = 'arraybuffer'; 1770 this.websocket.onopen = this.onOpen.bind(this); 1771 this.websocket.onmessage = this.onMessage.bind(this); 1772 this.websocket.onerror = this.onError.bind(this); 1773 this.websocket.onclose = this.onClose.bind(this); 1774 1775 this.disconnectCallback = disconnectCallback; 1776 1777 this.sendMessage = new RemoteMessage(this.websocket); 1778 this.sendMessage.allocate(1024); 1779 1780 this.receiveMessage = new RemoteMessage(); 1781 1782 this.container = document.createElement('div'); 1783 this.container.className = 'session'; 1784 this.container.style.position = 'relative'; 1785 targetElement.appendChild(this.container); 1786 1787 this.canvas = document.createElement('canvas'); 1788 this.canvas.className = 'session'; 1789 this.canvas.width = width; 1790 this.canvas.height = height; 1791 this.container.appendChild(this.canvas); 1792 1793 this.canvas.tabIndex = 0; 1794 this.canvas.focus(); 1795 1796 this.context = this.canvas.getContext('2d', { alpha: false }); 1797 this.context.imageSmoothingEnabled = false; 1798 1799 this.cursorVisible = true; 1800 this.cursorPosition = { x: 0, y: 0 }; 1801 this.cursorHotspot = { x: 0, y: 0 }; 1802 1803 this.states = new Object(); 1804 this.modifiers = 0; 1805 1806 this.canvas.onmousemove = this.onMouseMove.bind(this); 1807 this.canvas.onmousedown = this.onMouseDown.bind(this); 1808 this.canvas.onmouseup = this.onMouseUp.bind(this); 1809 this.canvas.onwheel = this.onWheel.bind(this); 1810 1811 this.canvas.onkeydown = this.onKeyDownUp.bind(this); 1812 this.canvas.onkeyup = this.onKeyDownUp.bind(this); 1813 this.canvas.onkeypress = this.onKeyPress.bind(this); 1814 1815 this.canvas.oncontextmenu = function(event) { 1816 event.preventDefault(); 1817 }; 1818 1819 this.canvas.onblur = function(event) { 1820 event.target.focus(); 1821 }; 1822} 1823 1824 1825RemoteDesktopSession.prototype.onOpen = function(open) 1826{ 1827 console.log('open:', open); 1828 this.init(); 1829} 1830 1831 1832RemoteDesktopSession.prototype.onMessage = function(message) 1833{ 1834 var data = message.data; 1835 if (this.messageRemainder) { 1836 var combined = new Uint8Array(this.messageRemainder.byteLength 1837 + data.byteLength); 1838 combined.set(new Uint8Array(this.messageRemainder), 0); 1839 combined.set(new Uint8Array(data), this.messageRemainder.byteLength); 1840 data = combined; 1841 1842 this.messageRemainder = null; 1843 } else 1844 data = new Uint8Array(data); 1845 1846 var byteOffset = 0; 1847 while (true) { 1848 try { 1849 if (!this.receiveMessage.attach(data, byteOffset)) 1850 break; 1851 } catch (exception) { 1852 // Discard everything and hope for the best. 1853 console.error('stream invalid, discarding everything', exception, 1854 this.receiveMessage, data, byteOffset); 1855 return; 1856 } 1857 1858 try { 1859 this.messageReceived(this.receiveMessage, this.sendMessage); 1860 } catch (exception) { 1861 console.error('exception during message processing:', exception); 1862 } 1863 1864 byteOffset += this.receiveMessage.size(); 1865 } 1866 1867 if (data.byteLength > byteOffset) 1868 this.messageRemainder = data.slice(byteOffset); 1869} 1870 1871 1872RemoteDesktopSession.prototype.messageReceived = function(remoteMessage, reply) 1873{ 1874 switch (remoteMessage.code()) { 1875 case RP_INIT_CONNECTION: 1876 console.log('init connection reply'); 1877 this.sendMessage.start(RP_UPDATE_DISPLAY_MODE); 1878 this.sendMessage.dataView.writeUint32(this.canvas.width); 1879 this.sendMessage.dataView.writeUint32(this.canvas.height); 1880 this.sendMessage.flush(); 1881 1882 this.sendMessage.start(RP_GET_SYSTEM_PALETTE); 1883 this.sendMessage.flush(); 1884 break; 1885 1886 case RP_GET_SYSTEM_PALETTE_RESULT: 1887 var count = remoteMessage.dataView.readUint32(); 1888 gSystemPalette = new Uint32Array(count); 1889 1890 var color = new RemoteColor(); 1891 for (var i = 0; i < gSystemPalette.length; i++) 1892 gSystemPalette[i] = color.readFrom(remoteMessage).toUint32(); 1893 1894 break; 1895 1896 case RP_CREATE_STATE: 1897 var token = remoteMessage.dataView.readInt32(); 1898 console.log('create state: ' + token); 1899 1900 if (this.states.hasOwnProperty(token)) 1901 console.error('create state for existing token: ' + token); 1902 1903 this.states[token] = new RemoteState(this, token); 1904 break; 1905 1906 case RP_DELETE_STATE: 1907 var token = remoteMessage.dataView.readInt32(); 1908 console.log('delete state: ' + token); 1909 1910 if (!this.states.hasOwnProperty(token)) { 1911 console.error('delete state for unknown token: ' + token); 1912 break; 1913 } 1914 1915 delete this.states[token]; 1916 break; 1917 1918 case RP_INVALIDATE_RECT: 1919 case RP_INVALIDATE_REGION: 1920 break; 1921 1922 case RP_SET_CURSOR: 1923 this.cursorHotspot = new RemotePoint(remoteMessage); 1924 var bitmap = new RemoteBitmap(remoteMessage); 1925 1926 bitmap.canvas.style.position = 'absolute'; 1927 if (this.cursorCanvas) 1928 this.cursorCanvas.remove(); 1929 1930 this.cursorCanvas = bitmap.canvas; 1931 this.cursorCanvas.style.pointerEvents = 'none'; 1932 this.container.appendChild(this.cursorCanvas); 1933 this.container.style.cursor = 'none'; 1934 this.updateCursor(); 1935 break; 1936 1937 case RP_MOVE_CURSOR_TO: 1938 this.cursorPosition.x = remoteMessage.dataView.readFloat32(); 1939 this.cursorPosition.y = remoteMessage.dataView.readFloat32(); 1940 this.updateCursor(); 1941 break; 1942 1943 case RP_SET_CURSOR_VISIBLE: 1944 this.cursorVisible = remoteMessage.dataView.readUint8(); 1945 if (this.cursorCanvas) { 1946 this.cursorCanvas.style.visibility 1947 = this.cursorVisible ? 'visible' : 'hidden'; 1948 } 1949 break; 1950 1951 case RP_COPY_RECT_NO_CLIPPING: 1952 var xOffset = remoteMessage.dataView.readInt32(); 1953 var yOffset = remoteMessage.dataView.readInt32(); 1954 var rect = new RemoteRect(remoteMessage); 1955 1956 var imageData = this.context.getImageData(rect.left, rect.top, 1957 rect.width(), rect.height()); 1958 this.context.putImageData(imageData, rect.left + xOffset, 1959 rect.top + yOffset); 1960 break; 1961 1962 case RP_FILL_REGION_COLOR_NO_CLIPPING: 1963 this.removeClipping(); 1964 this.context.currentToken = -1; 1965 this.context.resetTransform(); 1966 this.context.globalCompositeOperation = 'source-over'; 1967 1968 var rectCount = remoteMessage.dataView.readUint32(); 1969 var rects = new Array(rectCount); 1970 for (var i = 0; i < rectCount; i++) 1971 rects[i] = new RemoteRect(remoteMessage); 1972 1973 this.context.fillStyle = remoteMessage.readColor(); 1974 1975 for (var i = 0; i < rectCount; i++) 1976 rects[i].apply(this.context.fillRect.bind(this.context)); 1977 1978 break; 1979 1980 default: 1981 var token = remoteMessage.dataView.readInt32(); 1982 if (!this.states.hasOwnProperty(token)) { 1983 console.warn('no state for token: ' + token); 1984 this.states[token] = new RemoteState(this, token); 1985 } 1986 1987 this.states[token].messageReceived(remoteMessage, reply); 1988 break; 1989 } 1990} 1991 1992 1993RemoteDesktopSession.prototype.onError = function(error) 1994{ 1995 console.log('websocket error:', error); 1996 this.onDisconnect(error); 1997} 1998 1999 2000RemoteDesktopSession.prototype.onClose = function(close) 2001{ 2002 console.log('websocket close:', close); 2003 this.onDisconnect(close); 2004} 2005 2006 2007RemoteDesktopSession.prototype.onDisconnect = function(reason) 2008{ 2009 this.container.remove(); 2010 if (this.disconnectCallback) 2011 this.disconnectCallback(reason); 2012} 2013 2014 2015RemoteDesktopSession.prototype.applyClipping = function(clipRects) 2016{ 2017 this.removeClipping(); 2018 2019 if (!clipRects || clipRects.length == 0) 2020 return; 2021 2022 this.context.save(); 2023 this.context.beginPath(); 2024 2025 this.context.save(); 2026 this.context.lineJoin = 'miter'; 2027 this.context.miterLimit = 10; 2028 2029 for (var i = 0; i < clipRects.length; i++) 2030 clipRects[i].apply(this.context.rect.bind(this.context)); 2031 2032 this.context.restore(); 2033 2034 this.context.clip(); 2035 this.clippingApplied = true; 2036} 2037 2038 2039RemoteDesktopSession.prototype.removeClipping = function() 2040{ 2041 if (!this.clippingApplied) 2042 return; 2043 2044 this.context.restore(); 2045} 2046 2047 2048RemoteDesktopSession.prototype.init = function() 2049{ 2050 this.sendMessage.start(RP_INIT_CONNECTION); 2051 this.sendMessage.flush(); 2052} 2053 2054 2055RemoteDesktopSession.prototype.updateCursor = function() 2056{ 2057 if (!this.cursorVisible || !this.cursorCanvas) 2058 return; 2059 2060 this.cursorCanvas.style.left 2061 = (this.cursorPosition.x - this.cursorHotspot.x) + 'px'; 2062 this.cursorCanvas.style.top 2063 = (this.cursorPosition.y - this.cursorHotspot.y) + 'px'; 2064} 2065 2066 2067RemoteDesktopSession.prototype.onMouseMove = function(event) 2068{ 2069 this.sendMessage.start(RP_MOUSE_MOVED); 2070 this.sendMessage.dataView.writeFloat32(event.offsetX); 2071 this.sendMessage.dataView.writeFloat32(event.offsetY); 2072 this.sendMessage.flush(); 2073 event.preventDefault(); 2074} 2075 2076 2077RemoteDesktopSession.prototype.onMouseDown = function(event) 2078{ 2079 this.canvas.focus(); 2080 this.sendMessage.start(RP_MOUSE_DOWN); 2081 this.sendMessage.dataView.writeFloat32(event.offsetX); 2082 this.sendMessage.dataView.writeFloat32(event.offsetY); 2083 this.sendMessage.dataView.writeUint32(event.buttons); 2084 this.sendMessage.dataView.writeUint32(event.detail); 2085 this.sendMessage.flush(); 2086 event.preventDefault(); 2087} 2088 2089 2090RemoteDesktopSession.prototype.onMouseUp = function(event) 2091{ 2092 this.sendMessage.start(RP_MOUSE_UP); 2093 this.sendMessage.dataView.writeFloat32(event.offsetX); 2094 this.sendMessage.dataView.writeFloat32(event.offsetY); 2095 this.sendMessage.dataView.writeUint32(event.buttons); 2096 this.sendMessage.flush(); 2097 event.preventDefault(); 2098} 2099 2100 2101RemoteDesktopSession.prototype.onKeyDownUp = function(event) 2102{ 2103 var keyDown = event.type === 'keydown'; 2104 var lockModifier = false; 2105 var modifiersChanged = 0; 2106 switch (event.code) { 2107 case 'ShiftLeft': 2108 modifiersChanged |= B_LEFT_SHIFT_KEY; 2109 if (event.shiftKey == keyDown) 2110 modifiersChanged |= B_SHIFT_KEY; 2111 break; 2112 2113 case 'ShiftRight': 2114 modifiersChanged |= B_RIGHT_SHIFT_KEY; 2115 if (event.shiftKey == keyDown) 2116 modifiersChanged |= B_SHIFT_KEY; 2117 break; 2118 2119 case 'ControlLeft': 2120 modifiersChanged |= B_LEFT_CONTROL_KEY; 2121 if (event.ctrlKey == keyDown) 2122 modifiersChanged |= B_CONTROL_KEY; 2123 break; 2124 2125 case 'ControlRight': 2126 modifiersChanged |= B_RIGHT_CONTROL_KEY; 2127 if (event.ctrlKey == keyDown) 2128 modifiersChanged |= B_CONTROL_KEY; 2129 break; 2130 2131 case 'AltLeft': 2132 modifiersChanged |= B_LEFT_COMMAND_KEY; 2133 if (event.altKey == keyDown) 2134 modifiersChanged |= B_COMMAND_KEY; 2135 break; 2136 2137 case 'AltRight': 2138 modifiersChanged |= B_RIGHT_COMMAND_KEY; 2139 if (event.altKey == keyDown) 2140 modifiersChanged |= B_COMMAND_KEY; 2141 break; 2142 2143 case 'ContextMenu': 2144 modifiersChanged |= B_MENU_KEY; 2145 break; 2146 2147 case 'CapsLock': 2148 modifiersChanged |= B_CAPS_LOCK; 2149 lockModifier = true; 2150 break; 2151 2152 case 'ScrollLock': 2153 modifiersChanged |= B_SCROLL_LOCK; 2154 lockModifier = true; 2155 break; 2156 2157 case 'NumLock': 2158 modifiersChanged |= B_NUM_LOCK; 2159 lockModifier = true; 2160 break; 2161 } 2162 2163 if (modifiersChanged != 0) { 2164 if (lockModifier) { 2165 if (((this.modifiers & modifiersChanged) == 0) == keyDown) 2166 this.modifiers ^= modifiersChanged; 2167 } else { 2168 if (keyDown) 2169 this.modifiers |= modifiersChanged; 2170 else 2171 this.modifiers &= ~modifiersChanged; 2172 } 2173 2174 this.sendMessage.start(RP_MODIFIERS_CHANGED); 2175 this.sendMessage.dataView.writeUint32(this.modifiers); 2176 this.sendMessage.flush(); 2177 event.preventDefault(); 2178 return; 2179 } 2180 2181 this.sendMessage.start(keyDown ? RP_KEY_DOWN : RP_KEY_UP); 2182 if (event.key.length == 1) 2183 this.sendMessage.dataView.writeString(event.key); 2184 else { 2185 this.sendMessage.dataView.writeUint32(1); 2186 this.sendMessage.dataView.writeUint8(event.keyCode); 2187 } 2188 2189 if (event.keyCode) { 2190 this.sendMessage.dataView.writeUint32(0); 2191 this.sendMessage.dataView.writeUint32(event.keyCode); 2192 } 2193 2194 this.sendMessage.flush(); 2195 event.preventDefault(); 2196} 2197 2198 2199RemoteDesktopSession.prototype.onKeyPress = function(event) 2200{ 2201 this.sendMessage.start(RP_KEY_DOWN); 2202 this.sendMessage.dataView.writeUint32(1); 2203 this.sendMessage.dataView.writeUint8(event.which); 2204 this.sendMessage.flush(); 2205 this.sendMessage.start(RP_KEY_UP); 2206 this.sendMessage.dataView.writeUint32(1); 2207 this.sendMessage.dataView.writeUint8(event.which); 2208 this.sendMessage.flush(); 2209 event.preventDefault(); 2210} 2211 2212 2213RemoteDesktopSession.prototype.onWheel = function(event) 2214{ 2215 this.sendMessage.start(RP_MOUSE_WHEEL_CHANGED); 2216 this.sendMessage.dataView.writeFloat32(event.deltaX); 2217 this.sendMessage.dataView.writeFloat32(event.deltaY); 2218 this.sendMessage.flush(); 2219 event.preventDefault(); 2220} 2221 2222 2223function init() 2224{ 2225 var targetAddressInput = document.querySelector('#targetAddress'); 2226 var widthInput = document.querySelector('#width'); 2227 var heightInput = document.querySelector('#height'); 2228 2229 if (localStorage.targetAddress) 2230 targetAddressInput.value = localStorage.targetAddress; 2231 if (localStorage.width) 2232 widthInput.value = localStorage.width; 2233 if (localStorage.height) 2234 heightInput.value = localStorage.height; 2235 2236 var onDisconnect = function(reason) { 2237 document.body.classList.remove('connect'); 2238 gSession = undefined; 2239 }; 2240 2241 document.querySelector('#connectButton').onclick = function() { 2242 document.body.classList.add('connect'); 2243 2244 localStorage.width = widthInput.value; 2245 localStorage.height = heightInput.value; 2246 localStorage.targetAddress = targetAddressInput.value; 2247 2248 gSession = new RemoteDesktopSession(document.body, widthInput.value, 2249 heightInput.value, targetAddressInput.value, onDisconnect); 2250 }; 2251} 2252