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