@novnc/novnc 1.4.0-gcbbd9ab → 1.4.0-gd3aaf4d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/core/decoders/hextile.js +12 -22
  2. package/core/decoders/jpeg.js +111 -106
  3. package/core/decoders/raw.js +22 -29
  4. package/core/decoders/tight.js +3 -7
  5. package/core/decoders/zrle.js +1 -1
  6. package/core/deflator.js +2 -3
  7. package/core/display.js +13 -10
  8. package/core/encodings.js +1 -0
  9. package/core/inflator.js +1 -2
  10. package/core/input/keyboard.js +21 -13
  11. package/core/ra2.js +13 -7
  12. package/core/rfb.js +215 -285
  13. package/core/websock.js +69 -57
  14. package/lib/base64.js +5 -6
  15. package/lib/crypto/aes.js +7 -9
  16. package/lib/crypto/crypto.js +4 -5
  17. package/lib/crypto/des.js +6 -9
  18. package/lib/crypto/dh.js +5 -6
  19. package/lib/crypto/md5.js +2 -2
  20. package/lib/crypto/rsa.js +6 -7
  21. package/lib/decoders/copyrect.js +5 -6
  22. package/lib/decoders/hextile.js +26 -36
  23. package/lib/decoders/jpeg.js +132 -105
  24. package/lib/decoders/raw.js +27 -34
  25. package/lib/decoders/rre.js +5 -6
  26. package/lib/decoders/tight.js +18 -22
  27. package/lib/decoders/tightpng.js +18 -20
  28. package/lib/decoders/zrle.js +13 -15
  29. package/lib/deflator.js +12 -14
  30. package/lib/display.js +26 -24
  31. package/lib/encodings.js +2 -2
  32. package/lib/inflator.js +12 -14
  33. package/lib/input/domkeytable.js +1 -2
  34. package/lib/input/fixedkeys.js +2 -3
  35. package/lib/input/gesturehandler.js +5 -6
  36. package/lib/input/keyboard.js +33 -24
  37. package/lib/input/keysym.js +2 -3
  38. package/lib/input/keysymdef.js +2 -3
  39. package/lib/input/util.js +3 -3
  40. package/lib/input/vkeys.js +2 -3
  41. package/lib/input/xtscancodes.js +2 -3
  42. package/lib/ra2.js +89 -85
  43. package/lib/rfb.js +248 -290
  44. package/lib/util/browser.js +8 -11
  45. package/lib/util/cursor.js +9 -10
  46. package/lib/util/eventtarget.js +5 -6
  47. package/lib/util/logging.js +4 -9
  48. package/lib/vendor/pako/lib/utils/common.js +3 -6
  49. package/lib/vendor/pako/lib/zlib/constants.js +2 -3
  50. package/lib/vendor/pako/lib/zlib/deflate.js +24 -58
  51. package/lib/vendor/pako/lib/zlib/inffast.js +0 -2
  52. package/lib/vendor/pako/lib/zlib/inflate.js +17 -41
  53. package/lib/vendor/pako/lib/zlib/inftrees.js +3 -5
  54. package/lib/vendor/pako/lib/zlib/messages.js +2 -3
  55. package/lib/vendor/pako/lib/zlib/trees.js +3 -10
  56. package/lib/websock.js +97 -97
  57. package/package.json +1 -7
@@ -31,10 +31,7 @@ export default class HextileDecoder {
31
31
  return false;
32
32
  }
33
33
 
34
- let rQ = sock.rQ;
35
- let rQi = sock.rQi;
36
-
37
- let subencoding = rQ[rQi]; // Peek
34
+ let subencoding = sock.rQpeek8();
38
35
  if (subencoding > 30) { // Raw
39
36
  throw new Error("Illegal hextile subencoding (subencoding: " +
40
37
  subencoding + ")");
@@ -65,7 +62,7 @@ export default class HextileDecoder {
65
62
  return false;
66
63
  }
67
64
 
68
- let subrects = rQ[rQi + bytes - 1]; // Peek
65
+ let subrects = sock.rQpeekBytes(bytes).at(-1);
69
66
  if (subencoding & 0x10) { // SubrectsColoured
70
67
  bytes += subrects * (4 + 2);
71
68
  } else {
@@ -79,7 +76,7 @@ export default class HextileDecoder {
79
76
  }
80
77
 
81
78
  // We know the encoding and have a whole tile
82
- rQi++;
79
+ sock.rQshift8();
83
80
  if (subencoding === 0) {
84
81
  if (this._lastsubencoding & 0x01) {
85
82
  // Weird: ignore blanks are RAW
@@ -89,42 +86,36 @@ export default class HextileDecoder {
89
86
  }
90
87
  } else if (subencoding & 0x01) { // Raw
91
88
  let pixels = tw * th;
89
+ let data = sock.rQshiftBytes(pixels * 4, false);
92
90
  // Max sure the image is fully opaque
93
91
  for (let i = 0;i < pixels;i++) {
94
- rQ[rQi + i * 4 + 3] = 255;
92
+ data[i * 4 + 3] = 255;
95
93
  }
96
- display.blitImage(tx, ty, tw, th, rQ, rQi);
97
- rQi += bytes - 1;
94
+ display.blitImage(tx, ty, tw, th, data, 0);
98
95
  } else {
99
96
  if (subencoding & 0x02) { // Background
100
- this._background = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
101
- rQi += 4;
97
+ this._background = new Uint8Array(sock.rQshiftBytes(4));
102
98
  }
103
99
  if (subencoding & 0x04) { // Foreground
104
- this._foreground = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
105
- rQi += 4;
100
+ this._foreground = new Uint8Array(sock.rQshiftBytes(4));
106
101
  }
107
102
 
108
103
  this._startTile(tx, ty, tw, th, this._background);
109
104
  if (subencoding & 0x08) { // AnySubrects
110
- let subrects = rQ[rQi];
111
- rQi++;
105
+ let subrects = sock.rQshift8();
112
106
 
113
107
  for (let s = 0; s < subrects; s++) {
114
108
  let color;
115
109
  if (subencoding & 0x10) { // SubrectsColoured
116
- color = [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2], rQ[rQi + 3]];
117
- rQi += 4;
110
+ color = sock.rQshiftBytes(4);
118
111
  } else {
119
112
  color = this._foreground;
120
113
  }
121
- const xy = rQ[rQi];
122
- rQi++;
114
+ const xy = sock.rQshift8();
123
115
  const sx = (xy >> 4);
124
116
  const sy = (xy & 0x0f);
125
117
 
126
- const wh = rQ[rQi];
127
- rQi++;
118
+ const wh = sock.rQshift8();
128
119
  const sw = (wh >> 4) + 1;
129
120
  const sh = (wh & 0x0f) + 1;
130
121
 
@@ -133,7 +124,6 @@ export default class HextileDecoder {
133
124
  }
134
125
  this._finishTile(display);
135
126
  }
136
- sock.rQi = rQi;
137
127
  this._lastsubencoding = subencoding;
138
128
  this._tiles--;
139
129
  }
@@ -11,131 +11,136 @@ export default class JPEGDecoder {
11
11
  constructor() {
12
12
  // RealVNC will reuse the quantization tables
13
13
  // and Huffman tables, so we need to cache them.
14
- this._quantTables = [];
15
- this._huffmanTables = [];
16
14
  this._cachedQuantTables = [];
17
15
  this._cachedHuffmanTables = [];
18
16
 
19
- this._jpegLength = 0;
20
17
  this._segments = [];
21
18
  }
22
19
 
23
20
  decodeRect(x, y, width, height, sock, display, depth) {
24
21
  // A rect of JPEG encodings is simply a JPEG file
25
- if (!this._parseJPEG(sock.rQslice(0))) {
26
- return false;
27
- }
28
- const data = sock.rQshiftBytes(this._jpegLength);
29
- if (this._quantTables.length != 0 && this._huffmanTables.length != 0) {
30
- // If there are quantization tables and Huffman tables in the JPEG
31
- // image, we can directly render it.
32
- display.imageRect(x, y, width, height, "image/jpeg", data);
33
- return true;
34
- } else {
35
- // Otherwise we need to insert cached tables.
36
- const sofIndex = this._segments.findIndex(
37
- x => x[1] == 0xC0 || x[1] == 0xC2
38
- );
39
- if (sofIndex == -1) {
40
- throw new Error("Illegal JPEG image without SOF");
22
+ while (true) {
23
+ let segment = this._readSegment(sock);
24
+ if (segment === null) {
25
+ return false;
41
26
  }
42
- let segments = this._segments.slice(0, sofIndex);
43
- segments = segments.concat(this._quantTables.length ?
44
- this._quantTables :
45
- this._cachedQuantTables);
46
- segments.push(this._segments[sofIndex]);
47
- segments = segments.concat(this._huffmanTables.length ?
48
- this._huffmanTables :
49
- this._cachedHuffmanTables,
50
- this._segments.slice(sofIndex + 1));
51
- let length = 0;
52
- for (let i = 0; i < segments.length; i++) {
53
- length += segments[i].length;
27
+ this._segments.push(segment);
28
+ // End of image?
29
+ if (segment[1] === 0xD9) {
30
+ break;
54
31
  }
55
- const data = new Uint8Array(length);
56
- length = 0;
57
- for (let i = 0; i < segments.length; i++) {
58
- data.set(segments[i], length);
59
- length += segments[i].length;
32
+ }
33
+
34
+ let huffmanTables = [];
35
+ let quantTables = [];
36
+ for (let segment of this._segments) {
37
+ let type = segment[1];
38
+ if (type === 0xC4) {
39
+ // Huffman tables
40
+ huffmanTables.push(segment);
41
+ } else if (type === 0xDB) {
42
+ // Quantization tables
43
+ quantTables.push(segment);
60
44
  }
61
- display.imageRect(x, y, width, height, "image/jpeg", data);
62
- return true;
63
45
  }
64
- }
65
46
 
66
- _parseJPEG(buffer) {
67
- if (this._quantTables.length != 0) {
68
- this._cachedQuantTables = this._quantTables;
47
+ const sofIndex = this._segments.findIndex(
48
+ x => x[1] == 0xC0 || x[1] == 0xC2
49
+ );
50
+ if (sofIndex == -1) {
51
+ throw new Error("Illegal JPEG image without SOF");
52
+ }
53
+
54
+ if (quantTables.length === 0) {
55
+ this._segments.splice(sofIndex+1, 0,
56
+ ...this._cachedQuantTables);
57
+ }
58
+ if (huffmanTables.length === 0) {
59
+ this._segments.splice(sofIndex+1, 0,
60
+ ...this._cachedHuffmanTables);
61
+ }
62
+
63
+ let length = 0;
64
+ for (let segment of this._segments) {
65
+ length += segment.length;
66
+ }
67
+
68
+ let data = new Uint8Array(length);
69
+ length = 0;
70
+ for (let segment of this._segments) {
71
+ data.set(segment, length);
72
+ length += segment.length;
73
+ }
74
+
75
+ display.imageRect(x, y, width, height, "image/jpeg", data);
76
+
77
+ if (huffmanTables.length !== 0) {
78
+ this._cachedHuffmanTables = huffmanTables;
69
79
  }
70
- if (this._huffmanTables.length != 0) {
71
- this._cachedHuffmanTables = this._huffmanTables;
80
+ if (quantTables.length !== 0) {
81
+ this._cachedQuantTables = quantTables;
72
82
  }
73
- this._quantTables = [];
74
- this._huffmanTables = [];
83
+
75
84
  this._segments = [];
76
- let i = 0;
77
- let bufferLength = buffer.length;
78
- while (true) {
79
- let j = i;
80
- if (j + 2 > bufferLength) {
81
- return false;
82
- }
83
- if (buffer[j] != 0xFF) {
84
- throw new Error("Illegal JPEG marker received (byte: " +
85
- buffer[j] + ")");
86
- }
87
- const type = buffer[j+1];
88
- j += 2;
89
- if (type == 0xD9) {
90
- this._jpegLength = j;
91
- this._segments.push(buffer.slice(i, j));
92
- return true;
93
- } else if (type == 0xDA) {
94
- // start of scan
95
- let hasFoundEndOfScan = false;
96
- for (let k = j + 3; k + 1 < bufferLength; k++) {
97
- if (buffer[k] == 0xFF && buffer[k+1] != 0x00 &&
98
- !(buffer[k+1] >= 0xD0 && buffer[k+1] <= 0xD7)) {
99
- j = k;
100
- hasFoundEndOfScan = true;
101
- break;
102
- }
85
+
86
+ return true;
87
+ }
88
+
89
+ _readSegment(sock) {
90
+ if (sock.rQwait("JPEG", 2)) {
91
+ return null;
92
+ }
93
+
94
+ let marker = sock.rQshift8();
95
+ if (marker != 0xFF) {
96
+ throw new Error("Illegal JPEG marker received (byte: " +
97
+ marker + ")");
98
+ }
99
+ let type = sock.rQshift8();
100
+ if (type >= 0xD0 && type <= 0xD9 || type == 0x01) {
101
+ // No length after marker
102
+ return new Uint8Array([marker, type]);
103
+ }
104
+
105
+ if (sock.rQwait("JPEG", 2, 2)) {
106
+ return null;
107
+ }
108
+
109
+ let length = sock.rQshift16();
110
+ if (length < 2) {
111
+ throw new Error("Illegal JPEG length received (length: " +
112
+ length + ")");
113
+ }
114
+
115
+ if (sock.rQwait("JPEG", length-2, 4)) {
116
+ return null;
117
+ }
118
+
119
+ let extra = 0;
120
+ if (type === 0xDA) {
121
+ // start of scan
122
+ extra += 2;
123
+ while (true) {
124
+ if (sock.rQwait("JPEG", length-2+extra, 4)) {
125
+ return null;
103
126
  }
104
- if (!hasFoundEndOfScan) {
105
- return false;
127
+ let data = sock.rQpeekBytes(length-2+extra, false);
128
+ if (data.at(-2) === 0xFF && data.at(-1) !== 0x00 &&
129
+ !(data.at(-1) >= 0xD0 && data.at(-1) <= 0xD7)) {
130
+ extra -= 2;
131
+ break;
106
132
  }
107
- this._segments.push(buffer.slice(i, j));
108
- i = j;
109
- continue;
110
- } else if (type >= 0xD0 && type < 0xD9 || type == 0x01) {
111
- // No length after marker
112
- this._segments.push(buffer.slice(i, j));
113
- i = j;
114
- continue;
115
- }
116
- if (j + 2 > bufferLength) {
117
- return false;
118
- }
119
- const length = (buffer[j] << 8) + buffer[j+1] - 2;
120
- if (length < 0) {
121
- throw new Error("Illegal JPEG length received (length: " +
122
- length + ")");
123
- }
124
- j += 2;
125
- if (j + length > bufferLength) {
126
- return false;
133
+ extra++;
127
134
  }
128
- j += length;
129
- const segment = buffer.slice(i, j);
130
- if (type == 0xC4) {
131
- // Huffman tables
132
- this._huffmanTables.push(segment);
133
- } else if (type == 0xDB) {
134
- // Quantization tables
135
- this._quantTables.push(segment);
136
- }
137
- this._segments.push(segment);
138
- i = j;
139
135
  }
136
+
137
+ let segment = new Uint8Array(2 + length + extra);
138
+ segment[0] = marker;
139
+ segment[1] = type;
140
+ segment[2] = length >> 8;
141
+ segment[3] = length;
142
+ segment.set(sock.rQshiftBytes(length-2+extra, false), 4);
143
+
144
+ return segment;
140
145
  }
141
146
  }
@@ -24,41 +24,34 @@ export default class RawDecoder {
24
24
  const pixelSize = depth == 8 ? 1 : 4;
25
25
  const bytesPerLine = width * pixelSize;
26
26
 
27
- if (sock.rQwait("RAW", bytesPerLine)) {
28
- return false;
29
- }
27
+ while (this._lines > 0) {
28
+ if (sock.rQwait("RAW", bytesPerLine)) {
29
+ return false;
30
+ }
30
31
 
31
- const curY = y + (height - this._lines);
32
- const currHeight = Math.min(this._lines,
33
- Math.floor(sock.rQlen / bytesPerLine));
34
- const pixels = width * currHeight;
32
+ const curY = y + (height - this._lines);
35
33
 
36
- let data = sock.rQ;
37
- let index = sock.rQi;
34
+ let data = sock.rQshiftBytes(bytesPerLine, false);
38
35
 
39
- // Convert data if needed
40
- if (depth == 8) {
41
- const newdata = new Uint8Array(pixels * 4);
42
- for (let i = 0; i < pixels; i++) {
43
- newdata[i * 4 + 0] = ((data[index + i] >> 0) & 0x3) * 255 / 3;
44
- newdata[i * 4 + 1] = ((data[index + i] >> 2) & 0x3) * 255 / 3;
45
- newdata[i * 4 + 2] = ((data[index + i] >> 4) & 0x3) * 255 / 3;
46
- newdata[i * 4 + 3] = 255;
36
+ // Convert data if needed
37
+ if (depth == 8) {
38
+ const newdata = new Uint8Array(width * 4);
39
+ for (let i = 0; i < width; i++) {
40
+ newdata[i * 4 + 0] = ((data[i] >> 0) & 0x3) * 255 / 3;
41
+ newdata[i * 4 + 1] = ((data[i] >> 2) & 0x3) * 255 / 3;
42
+ newdata[i * 4 + 2] = ((data[i] >> 4) & 0x3) * 255 / 3;
43
+ newdata[i * 4 + 3] = 255;
44
+ }
45
+ data = newdata;
47
46
  }
48
- data = newdata;
49
- index = 0;
50
- }
51
47
 
52
- // Max sure the image is fully opaque
53
- for (let i = 0; i < pixels; i++) {
54
- data[index + i * 4 + 3] = 255;
55
- }
48
+ // Max sure the image is fully opaque
49
+ for (let i = 0; i < width; i++) {
50
+ data[i * 4 + 3] = 255;
51
+ }
56
52
 
57
- display.blitImage(x, curY, width, currHeight, data, index);
58
- sock.rQskipBytes(currHeight * bytesPerLine);
59
- this._lines -= currHeight;
60
- if (this._lines > 0) {
61
- return false;
53
+ display.blitImage(x, curY, width, 1, data, 0);
54
+ this._lines--;
62
55
  }
63
56
 
64
57
  return true;
@@ -76,12 +76,8 @@ export default class TightDecoder {
76
76
  return false;
77
77
  }
78
78
 
79
- const rQi = sock.rQi;
80
- const rQ = sock.rQ;
81
-
82
- display.fillRect(x, y, width, height,
83
- [rQ[rQi], rQ[rQi + 1], rQ[rQi + 2]], false);
84
- sock.rQskipBytes(3);
79
+ let pixel = sock.rQshiftBytes(3);
80
+ display.fillRect(x, y, width, height, pixel, false);
85
81
 
86
82
  return true;
87
83
  }
@@ -316,7 +312,7 @@ export default class TightDecoder {
316
312
  return null;
317
313
  }
318
314
 
319
- let data = sock.rQshiftBytes(this._len);
315
+ let data = sock.rQshiftBytes(this._len, false);
320
316
  this._len = 0;
321
317
 
322
318
  return data;
@@ -32,7 +32,7 @@ export default class ZRLEDecoder {
32
32
  return false;
33
33
  }
34
34
 
35
- const data = sock.rQshiftBytes(this._length);
35
+ const data = sock.rQshiftBytes(this._length, false);
36
36
 
37
37
  this._inflator.setInput(data);
38
38
 
package/core/deflator.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import { deflateInit, deflate } from "../vendor/pako/lib/zlib/deflate.js";
10
- import { Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
10
+ import { Z_FULL_FLUSH, Z_DEFAULT_COMPRESSION } from "../vendor/pako/lib/zlib/deflate.js";
11
11
  import ZStream from "../vendor/pako/lib/zlib/zstream.js";
12
12
 
13
13
  export default class Deflator {
@@ -15,9 +15,8 @@ export default class Deflator {
15
15
  this.strm = new ZStream();
16
16
  this.chunkSize = 1024 * 10 * 10;
17
17
  this.outputBuffer = new Uint8Array(this.chunkSize);
18
- this.windowBits = 5;
19
18
 
20
- deflateInit(this.strm, this.windowBits);
19
+ deflateInit(this.strm, Z_DEFAULT_COMPRESSION);
21
20
  }
22
21
 
23
22
  deflate(inData) {
package/core/display.js CHANGED
@@ -15,7 +15,7 @@ export default class Display {
15
15
  this._drawCtx = null;
16
16
 
17
17
  this._renderQ = []; // queue drawing actions for in-oder rendering
18
- this._flushing = false;
18
+ this._flushPromise = null;
19
19
 
20
20
  // the full frame buffer (logical canvas) size
21
21
  this._fbWidth = 0;
@@ -61,10 +61,6 @@ export default class Display {
61
61
 
62
62
  this._scale = 1.0;
63
63
  this._clipViewport = false;
64
-
65
- // ===== EVENT HANDLERS =====
66
-
67
- this.onflush = () => {}; // A flush request has finished
68
64
  }
69
65
 
70
66
  // ===== PROPERTIES =====
@@ -306,9 +302,14 @@ export default class Display {
306
302
 
307
303
  flush() {
308
304
  if (this._renderQ.length === 0) {
309
- this.onflush();
305
+ return Promise.resolve();
310
306
  } else {
311
- this._flushing = true;
307
+ if (this._flushPromise === null) {
308
+ this._flushPromise = new Promise((resolve) => {
309
+ this._flushResolve = resolve;
310
+ });
311
+ }
312
+ return this._flushPromise;
312
313
  }
313
314
  }
314
315
 
@@ -517,9 +518,11 @@ export default class Display {
517
518
  }
518
519
  }
519
520
 
520
- if (this._renderQ.length === 0 && this._flushing) {
521
- this._flushing = false;
522
- this.onflush();
521
+ if (this._renderQ.length === 0 &&
522
+ this._flushPromise !== null) {
523
+ this._flushResolve();
524
+ this._flushPromise = null;
525
+ this._flushResolve = null;
523
526
  }
524
527
  }
525
528
  }
package/core/encodings.js CHANGED
@@ -22,6 +22,7 @@ export const encodings = {
22
22
  pseudoEncodingLastRect: -224,
23
23
  pseudoEncodingCursor: -239,
24
24
  pseudoEncodingQEMUExtendedKeyEvent: -258,
25
+ pseudoEncodingQEMULedEvent: -261,
25
26
  pseudoEncodingDesktopName: -307,
26
27
  pseudoEncodingExtendedDesktopSize: -308,
27
28
  pseudoEncodingXvp: -309,
package/core/inflator.js CHANGED
@@ -14,9 +14,8 @@ export default class Inflate {
14
14
  this.strm = new ZStream();
15
15
  this.chunkSize = 1024 * 10 * 10;
16
16
  this.strm.output = new Uint8Array(this.chunkSize);
17
- this.windowBits = 5;
18
17
 
19
- inflateInit(this.strm, this.windowBits);
18
+ inflateInit(this.strm);
20
19
  }
21
20
 
22
21
  setInput(data) {
@@ -36,7 +36,7 @@ export default class Keyboard {
36
36
 
37
37
  // ===== PRIVATE METHODS =====
38
38
 
39
- _sendKeyEvent(keysym, code, down) {
39
+ _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {
40
40
  if (down) {
41
41
  this._keyDownList[code] = keysym;
42
42
  } else {
@@ -48,8 +48,8 @@ export default class Keyboard {
48
48
  }
49
49
 
50
50
  Log.Debug("onkeyevent " + (down ? "down" : "up") +
51
- ", keysym: " + keysym, ", code: " + code);
52
- this.onkeyevent(keysym, code, down);
51
+ ", keysym: " + keysym, ", code: " + code, + ", numlock: " + numlock + ", capslock: " + capslock);
52
+ this.onkeyevent(keysym, code, down, numlock, capslock);
53
53
  }
54
54
 
55
55
  _getKeyCode(e) {
@@ -86,6 +86,14 @@ export default class Keyboard {
86
86
  _handleKeyDown(e) {
87
87
  const code = this._getKeyCode(e);
88
88
  let keysym = KeyboardUtil.getKeysym(e);
89
+ let numlock = e.getModifierState('NumLock');
90
+ let capslock = e.getModifierState('CapsLock');
91
+
92
+ // getModifierState for NumLock is not supported on mac and ios and always returns false.
93
+ // Set to null to indicate unknown/unsupported instead.
94
+ if (browser.isMac() || browser.isIOS()) {
95
+ numlock = null;
96
+ }
89
97
 
90
98
  // Windows doesn't have a proper AltGr, but handles it using
91
99
  // fake Ctrl+Alt. However the remote end might not be Windows,
@@ -107,7 +115,7 @@ export default class Keyboard {
107
115
  // key to "AltGraph".
108
116
  keysym = KeyTable.XK_ISO_Level3_Shift;
109
117
  } else {
110
- this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
118
+ this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);
111
119
  }
112
120
  }
113
121
 
@@ -118,8 +126,8 @@ export default class Keyboard {
118
126
  // If it's a virtual keyboard then it should be
119
127
  // sufficient to just send press and release right
120
128
  // after each other
121
- this._sendKeyEvent(keysym, code, true);
122
- this._sendKeyEvent(keysym, code, false);
129
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
130
+ this._sendKeyEvent(keysym, code, false, numlock, capslock);
123
131
  }
124
132
 
125
133
  stopEvent(e);
@@ -157,8 +165,8 @@ export default class Keyboard {
157
165
  // while meta is held down
158
166
  if ((browser.isMac() || browser.isIOS()) &&
159
167
  (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {
160
- this._sendKeyEvent(keysym, code, true);
161
- this._sendKeyEvent(keysym, code, false);
168
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
169
+ this._sendKeyEvent(keysym, code, false, numlock, capslock);
162
170
  stopEvent(e);
163
171
  return;
164
172
  }
@@ -168,8 +176,8 @@ export default class Keyboard {
168
176
  // which toggles on each press, but not on release. So pretend
169
177
  // it was a quick press and release of the button.
170
178
  if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {
171
- this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
172
- this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
179
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);
180
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);
173
181
  stopEvent(e);
174
182
  return;
175
183
  }
@@ -182,8 +190,8 @@ export default class Keyboard {
182
190
  KeyTable.XK_Hiragana,
183
191
  KeyTable.XK_Romaji ];
184
192
  if (browser.isWindows() && jpBadKeys.includes(keysym)) {
185
- this._sendKeyEvent(keysym, code, true);
186
- this._sendKeyEvent(keysym, code, false);
193
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
194
+ this._sendKeyEvent(keysym, code, false, numlock, capslock);
187
195
  stopEvent(e);
188
196
  return;
189
197
  }
@@ -199,7 +207,7 @@ export default class Keyboard {
199
207
  return;
200
208
  }
201
209
 
202
- this._sendKeyEvent(keysym, code, true);
210
+ this._sendKeyEvent(keysym, code, true, numlock, capslock);
203
211
  }
204
212
 
205
213
  _handleKeyUp(e) {