From effc0eee02812f9b588873d462bccc17dd9355c8 Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Thu, 16 Sep 2021 15:32:26 +0300 Subject: [PATCH 01/12] Initial binary clipboard support --- core/rfb.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/core/rfb.js b/core/rfb.js index 41b94f07..63b16f26 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2388,6 +2388,26 @@ export default class RFB extends EventTargetMixin { return true; } + _handleBinaryClipboard() { + Log.Debug("HandleBinaryClipboard"); + + let num = this._sock.rQshift8(); // how many different mime types + + for (let i = 0; i < num; i++) { + let mimelen = this._sock.rQshift8(); + const mime = this._sock.rQshiftStr(mimelen); + + let len = this._sock.rQshift32(); + + const data = this._sock.rQshiftStr(len); + + // TODO, what do we do with this? + console.log("Mime " + mime + ", len ", len); + } + + return true; + } + _handle_server_stats_msg() { this._sock.rQskipBytes(3); // Padding const length = this._sock.rQshift32(); @@ -2528,6 +2548,10 @@ export default class RFB extends EventTargetMixin { this._trackFrameStats = true; return true; + case 180: // KASM binary clipboard + this._handleBinaryClipboard(); + return true; + case 248: // ServerFence return this._handleServerFenceMsg(); @@ -3183,6 +3207,49 @@ RFB.messages = { }, + sendBinaryClipboard(sock, data, mime) { + const buff = sock._sQ; + const offset = sock._sQlen; + + buff[offset] = 180; // msg-type + + buff[offset + 1] = 1; // we're sending one mime type + buff[offset + 2] = mime.length; + + for (let i = 0; i < mime.length; i++) { + buff[offset + 3 + i] = mime.charCodeAt(i); // change to [] if not a string + } + + let length = data.length; + + buff[offset + 3 + mime.length] = length >> 24; + buff[offset + 3 + mime.length + 1] = length >> 16; + buff[offset + 3 + mime.length + 2] = length >> 8; + buff[offset + 3 + mime.length + 3] = length; + + sock._sQlen += 3 + mime.length + 4; + + // We have to keep track of from where in the data we begin creating the + // buffer for the flush in the next iteration. + let dataOffset = 0; + + let remaining = data.length; + while (remaining > 0) { + + let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen)); + for (let i = 0; i < flushSize; i++) { + buff[sock._sQlen + i] = data[dataOffset + i]; + } + + sock._sQlen += flushSize; + sock.flush(); + + remaining -= flushSize; + dataOffset += flushSize; + } + + }, + setDesktopSize(sock, width, height, id, flags) { const buff = sock._sQ; const offset = sock._sQlen; From f165845c2db973dfef40b758775ad51cd7c43e79 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 16 Sep 2021 19:16:49 +0000 Subject: [PATCH 02/12] KASM-1947 binary clipboard WIP --- core/rfb.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/rfb.js b/core/rfb.js index 63b16f26..9a341115 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2392,6 +2392,8 @@ export default class RFB extends EventTargetMixin { Log.Debug("HandleBinaryClipboard"); let num = this._sock.rQshift8(); // how many different mime types + let blobs = []; + let clipdata = []; for (let i = 0; i < num; i++) { let mimelen = this._sock.rQshift8(); @@ -2399,10 +2401,25 @@ export default class RFB extends EventTargetMixin { let len = this._sock.rQshift32(); - const data = this._sock.rQshiftStr(len); + const data = this._sock.rQshiftBytes(len); // TODO, what do we do with this? console.log("Mime " + mime + ", len ", len); + + switch(mime) { + case "image/png": + let blob = new Blob([data], { type: mime }); + clipdata.push(new ClipboardItem({ [mime]: blob })); + break; + } + } + + if (clipdata.length > 0) { + navigator.clipboard.write(clipdata).then( + function() {}, + function(err) { + console.log("Error writing to client clipboard: " + err); + }); } return true; From 3c477925f334fe63966e2f7c91fa38622c5457fa Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 17 Sep 2021 13:47:11 +0000 Subject: [PATCH 03/12] KASM-1947 send png WIP --- app/ui.js | 21 +++++++++++++++++++-- core/rfb.js | 40 ++++++++++++++++++++++++++++++++++++++++ core/util/int.js | 11 +++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/app/ui.js b/app/ui.js index d41d1aa1..e349c5bf 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1125,12 +1125,25 @@ const UI = { UI.copyFromLocalClipboard(); }, copyFromLocalClipboard: function copyFromLocalClipboard() { + console.log("copyFromLocalClipboard Called"); + if (!document.hasFocus()) { + console.log("window does not have focus"); + return; + } if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) { - UI.readClipboard(function (text) { + console.log('checking clipboard'); + navigator.clipboard.read().then((data) => { + console.log('sending clipboard data to server'); + UI.rfb.clipboardPasteDataFrom(data); + }); + + /*UI.readClipboard(function (text) { + console.log("clipboard read"); var maximumBufferSize = 10000; var clipVal = document.getElementById('noVNC_clipboard_text').value; if (clipVal != text) { + console.log("clipboard sent") document.getElementById('noVNC_clipboard_text').value = text; // The websocket has a maximum buffer array size if (text.length > maximumBufferSize) { @@ -1144,7 +1157,7 @@ const UI = { UI.needToCheckClipboardChange = false; - }); + }); */ } }, @@ -1285,6 +1298,10 @@ const UI = { UI.rfb.clipboardUp = UI.getSetting('clipboard_up'); UI.rfb.clipboardDown = UI.getSetting('clipboard_down'); UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless'); + if (UI.rfb.clipboardSeamless) { + // explicitly request permission to the clipboard + navigator.permissions.query({ name: "clipboard-read" }).then((result) => { console.log('binary clipboard enabled') }); + } // KASM-960 workaround, disable seamless on Safari if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { diff --git a/core/rfb.js b/core/rfb.js index 9a341115..a2196fe8 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -10,6 +10,7 @@ import { toUnsigned32bit, toSigned32bit } from './util/int.js'; import * as Log from './util/logging.js'; import { encodeUTF8, decodeUTF8 } from './util/strings.js'; +import { hashUInt8Array } from './util/int.js'; import { dragThreshold, supportsCursorURIs, isTouchDevice, isMac } from './util/browser.js'; import { clientToElement } from './util/element.js'; import { setCapture } from './util/events.js'; @@ -347,6 +348,7 @@ export default class RFB extends EventTargetMixin { this._qualityLevel = 6; this._compressionLevel = 2; + this._clipHash = 0; } // ===== PROPERTIES ===== @@ -790,6 +792,43 @@ export default class RFB extends EventTargetMixin { } } + async clipboardPasteDataFrom(clipdata) { + if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; } + this.sentEventsCounter+=1; + + let dataset = []; + let mimes = []; + for (let i = 0; i < clipdata.length; i++) { + for (let ti = 0; ti < clipdata[i].types.length; ti++) { + let mime = clipdata[i].types[ti]; + if (mime !== 'image/png') { + console.log('skipping type: ' + mime); + continue; + } + mimes.push(mime); + let blob = await clipdata[i].getType(mime); + let buff = await blob.arrayBuffer(); + let data = new Uint8Array(buff); + + let h = hashUInt8Array(data); + console.log('New Clip hash: ' + h); + if (h === this._clipHash) { + return; + } else { + this._clipHash = h; + } + dataset.push(data); + console.log('Sending mime type: ' + mime); + } + } + + + if (dataset.length > 0) { + RFB.messages.sendBinaryClipboard(this._sock, dataset[0], mimes[0]); + } + + } + requestBottleneckStats() { RFB.messages.requestStats(this._sock); } @@ -3265,6 +3304,7 @@ RFB.messages = { dataOffset += flushSize; } + console.log('clipboard sent'); }, setDesktopSize(sock, width, height, id, flags) { diff --git a/core/util/int.js b/core/util/int.js index 001f40f2..79c9f724 100644 --- a/core/util/int.js +++ b/core/util/int.js @@ -13,3 +13,14 @@ export function toUnsigned32bit(toConvert) { export function toSigned32bit(toConvert) { return toConvert | 0; } + +/* + * Fast hashing function with low entropy, not for security uses. +*/ +export function hashUInt8Array(data) { + let h; + for (let i = 0; i < data.length; i++) { + h = Math.imul(31, h) + data[i] | 0; + } + return h; +} \ No newline at end of file From 141f91fde51a4a2d039ba2aa5944a53408b5ba0f Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 21 Sep 2021 09:38:34 +0000 Subject: [PATCH 04/12] KASM-1947 WIP --- core/rfb.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/rfb.js b/core/rfb.js index a2196fe8..afce1c10 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -805,6 +805,7 @@ export default class RFB extends EventTargetMixin { console.log('skipping type: ' + mime); continue; } + mimes.push(mime); let blob = await clipdata[i].getType(mime); let buff = await blob.arrayBuffer(); @@ -2447,9 +2448,14 @@ export default class RFB extends EventTargetMixin { switch(mime) { case "image/png": + case "text/html": + case "text/plain": let blob = new Blob([data], { type: mime }); clipdata.push(new ClipboardItem({ [mime]: blob })); break; + default: + console.log('Mime type skipped: ' + mime); + break; } } From 34bfdddab59de445cf8e3a352b07a6ed6e575024 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 23 Sep 2021 08:39:37 +0000 Subject: [PATCH 05/12] KASM-1947 binary clipboard WIP --- app/ui.js | 3 -- core/rfb.js | 132 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 82 insertions(+), 53 deletions(-) diff --git a/app/ui.js b/app/ui.js index e349c5bf..366e16e5 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1125,15 +1125,12 @@ const UI = { UI.copyFromLocalClipboard(); }, copyFromLocalClipboard: function copyFromLocalClipboard() { - console.log("copyFromLocalClipboard Called"); if (!document.hasFocus()) { console.log("window does not have focus"); return; } if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) { - console.log('checking clipboard'); navigator.clipboard.read().then((data) => { - console.log('sending clipboard data to server'); UI.rfb.clipboardPasteDataFrom(data); }); diff --git a/core/rfb.js b/core/rfb.js index afce1c10..1014778e 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -798,34 +798,54 @@ export default class RFB extends EventTargetMixin { let dataset = []; let mimes = []; + let h = 0; for (let i = 0; i < clipdata.length; i++) { for (let ti = 0; ti < clipdata[i].types.length; ti++) { let mime = clipdata[i].types[ti]; - if (mime !== 'image/png') { - console.log('skipping type: ' + mime); - continue; + + switch (mime) { + case 'image/png': + case 'text/plain': + case 'text/html': + mimes.push(mime); + let blob = await clipdata[i].getType(mime); + let buff = await blob.arrayBuffer(); + let data = new Uint8Array(buff); + + if (!h) { + h = hashUInt8Array(data); + if (h === this._clipHash) { + console.log('No clipboard changes'); + return; + } else { + this._clipHash = h; + } + } + dataset.push(data); + console.log('Sending mime type: ' + mime); + break; + default: + console.log('skipping clip send mime type: ' + mime) } - mimes.push(mime); - let blob = await clipdata[i].getType(mime); - let buff = await blob.arrayBuffer(); - let data = new Uint8Array(buff); - - let h = hashUInt8Array(data); - console.log('New Clip hash: ' + h); - if (h === this._clipHash) { - return; - } else { - this._clipHash = h; - } - dataset.push(data); - console.log('Sending mime type: ' + mime); } } + //if png is present and text/plain is not, remove other variations of images to save bandwidth + //if png is present with text/plain, then remove png. Word will put in a png of copied text + if (mimes.includes('image/png') && !mimes.includes('text/plain')) { + let i = mimes.indexOf('image/png'); + mimes = mimes.slice(i, i+1); + dataset = dataset.slice(i, i+1); + } else if (mimes.includes('image/png') && mimes.includes('text/plain')) { + let i = mimes.indexOf('image/png'); + mimes.splice(i, 1); + dataset.splice(i, 1); + } + if (dataset.length > 0) { - RFB.messages.sendBinaryClipboard(this._sock, dataset[0], mimes[0]); + RFB.messages.sendBinaryClipboard(this._sock, dataset, mimes); } } @@ -2460,6 +2480,7 @@ export default class RFB extends EventTargetMixin { } if (clipdata.length > 0) { + this._clipHash = 0; navigator.clipboard.write(clipdata).then( function() {}, function(err) { @@ -3269,48 +3290,59 @@ RFB.messages = { }, - sendBinaryClipboard(sock, data, mime) { + sendBinaryClipboard(sock, dataset, mimes) { + + const buff = sock._sQ; - const offset = sock._sQlen; + let offset = sock._sQlen; buff[offset] = 180; // msg-type + buff[offset + 1] = dataset.length; // how many mime types + sock._sQlen += 2; + offset += 2; - buff[offset + 1] = 1; // we're sending one mime type - buff[offset + 2] = mime.length; + for (let i=0; i < dataset.length; i++) { + let mime = mimes[i]; + let data = dataset[i]; - for (let i = 0; i < mime.length; i++) { - buff[offset + 3 + i] = mime.charCodeAt(i); // change to [] if not a string - } + buff[offset++] = mime.length; - let length = data.length; - - buff[offset + 3 + mime.length] = length >> 24; - buff[offset + 3 + mime.length + 1] = length >> 16; - buff[offset + 3 + mime.length + 2] = length >> 8; - buff[offset + 3 + mime.length + 3] = length; - - sock._sQlen += 3 + mime.length + 4; - - // We have to keep track of from where in the data we begin creating the - // buffer for the flush in the next iteration. - let dataOffset = 0; - - let remaining = data.length; - while (remaining > 0) { - - let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen)); - for (let i = 0; i < flushSize; i++) { - buff[sock._sQlen + i] = data[dataOffset + i]; + for (let i = 0; i < mime.length; i++) { + buff[offset++] = mime.charCodeAt(i); // change to [] if not a string } - sock._sQlen += flushSize; - sock.flush(); + let length = data.length; - remaining -= flushSize; - dataOffset += flushSize; + console.log('Clipboard data sent mime type ' + mime + ' len ' + length); + + buff[offset++] = length >> 24; + buff[offset++] = length >> 16; + buff[offset++] = length >> 8; + buff[offset++] = length; + + sock._sQlen += 1 + mime.length + 4; + + // We have to keep track of from where in the data we begin creating the + // buffer for the flush in the next iteration. + let dataOffset = 0; + + let remaining = data.length; + while (remaining > 0) { + + let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen)); + for (let i = 0; i < flushSize; i++) { + buff[sock._sQlen + i] = data[dataOffset + i]; + } + + sock._sQlen += flushSize; + sock.flush(); + + remaining -= flushSize; + dataOffset += flushSize; + } + + offset = sock._sQlen; } - - console.log('clipboard sent'); }, setDesktopSize(sock, width, height, id, flags) { From e7c601efc07b64fefe194f52a6cdf07a50025eff Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 1 Oct 2021 09:03:03 +0000 Subject: [PATCH 06/12] clipboard WIP --- app/ui.js | 10 ++++++++-- core/rfb.js | 22 ++++++++++++++++++---- core/util/browser.js | 4 ++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/app/ui.js b/app/ui.js index 366e16e5..2fed9456 100644 --- a/app/ui.js +++ b/app/ui.js @@ -22,7 +22,7 @@ window.addEventListener("load", function() { import * as Log from '../core/util/logging.js'; import _, { l10n } from './localization.js'; -import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold } +import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold, supportsBinaryClipboard, isFirefox } from '../core/util/browser.js'; import { setCapture, getPointerEvent } from '../core/util/events.js'; import KeyTable from "../core/input/keysym.js"; @@ -1132,6 +1132,7 @@ const UI = { if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) { navigator.clipboard.read().then((data) => { UI.rfb.clipboardPasteDataFrom(data); + UI.needToCheckClipboardChange = false; }); /*UI.readClipboard(function (text) { @@ -1294,10 +1295,15 @@ const UI = { UI.rfb.videoQuality = UI.getSetting('video_quality'); UI.rfb.clipboardUp = UI.getSetting('clipboard_up'); UI.rfb.clipboardDown = UI.getSetting('clipboard_down'); + UI.rfb.clipboardBinary = supportsBinaryClipboard(); UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless'); if (UI.rfb.clipboardSeamless) { // explicitly request permission to the clipboard - navigator.permissions.query({ name: "clipboard-read" }).then((result) => { console.log('binary clipboard enabled') }); + if (UI.rfb.clipboardBinary) { + navigator.permissions.query({ name: "clipboard-read" }).then((result) => { console.log('binary clipboard enabled') }); + } else { + navigator.permissions.query({ name: "clipboardRead" }).then((result) => { console.log('binary clipboard enabled') }); + } } // KASM-960 workaround, disable seamless on Safari if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) diff --git a/core/rfb.js b/core/rfb.js index 1014778e..8c87b764 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -142,6 +142,7 @@ export default class RFB extends EventTargetMixin { this._frameRate = 30; this._maxVideoResolutionX = 960; this._maxVideoResolutionY = 540; + this._clipboardBinary = true; this._trackFrameStats = false; @@ -353,6 +354,9 @@ export default class RFB extends EventTargetMixin { // ===== PROPERTIES ===== + get clipboardBinary() { return this._clipboardMode; } + set clipboardBinary(val) { this._clipboardMode = val; } + get videoQuality() { return this._videoQuality; } set videoQuality(quality) { this._videoQuality = quality; } @@ -807,7 +811,6 @@ export default class RFB extends EventTargetMixin { case 'image/png': case 'text/plain': case 'text/html': - mimes.push(mime); let blob = await clipdata[i].getType(mime); let buff = await blob.arrayBuffer(); let data = new Uint8Array(buff); @@ -821,6 +824,12 @@ export default class RFB extends EventTargetMixin { this._clipHash = h; } } + + if (mimes.includes(mime)) { + continue; + } + + mimes.push(mime); dataset.push(data); console.log('Sending mime type: ' + mime); break; @@ -2454,6 +2463,8 @@ export default class RFB extends EventTargetMixin { let num = this._sock.rQshift8(); // how many different mime types let blobs = []; let clipdata = []; + let mimes = []; + console.log('Clipboard items recieved.'); for (let i = 0; i < num; i++) { let mimelen = this._sock.rQshift8(); @@ -2462,14 +2473,17 @@ export default class RFB extends EventTargetMixin { let len = this._sock.rQshift32(); const data = this._sock.rQshiftBytes(len); - - // TODO, what do we do with this? - console.log("Mime " + mime + ", len ", len); switch(mime) { case "image/png": case "text/html": case "text/plain": + if (mimes.includes(mime)){ + continue; + } + mimes.push(mime); + console.log("Mime " + mime + ", len ", len); + console.log(data); let blob = new Blob([data], { type: mime }); clipdata.push(new ClipboardItem({ [mime]: blob })); break; diff --git a/core/util/browser.js b/core/util/browser.js index 24b5e960..95a7ada7 100644 --- a/core/util/browser.js +++ b/core/util/browser.js @@ -101,3 +101,7 @@ export function isFirefox() { return navigator && !!(/firefox/i).exec(navigator.userAgent); } +export function supportsBinaryClipboard() { + return (typeof navigator.clipboard.read === "function"); +} + From 8842f372b8cead623eabb5059237067ebf2769fb Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 1 Oct 2021 19:02:51 +0000 Subject: [PATCH 07/12] WIP, firefox support --- app/ui.js | 45 ++++++++------------------- core/rfb.js | 87 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/app/ui.js b/app/ui.js index e98ff696..4803e9e6 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1134,32 +1134,15 @@ const UI = { return; } if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) { - navigator.clipboard.read().then((data) => { - UI.rfb.clipboardPasteDataFrom(data); - UI.needToCheckClipboardChange = false; - }); - /*UI.readClipboard(function (text) { - console.log("clipboard read"); - var maximumBufferSize = 10000; - var clipVal = document.getElementById('noVNC_clipboard_text').value; - - if (clipVal != text) { - console.log("clipboard sent") - document.getElementById('noVNC_clipboard_text').value = text; // The websocket has a maximum buffer array size - - if (text.length > maximumBufferSize) { - UI.popupMessage("Clipboard contents too large. Data truncated", 2000); - UI.rfb.clipboardPasteFrom(text.slice(0, maximumBufferSize)); - } else { - //UI.popupMessage("Copied from Local Clipboard"); - UI.rfb.clipboardPasteFrom(text); - } - } // Reset flag to prevent checking too often - - - UI.needToCheckClipboardChange = false; - }); */ + if (UI.rfb.clipboardBinary) { + navigator.clipboard.read().then((data) => { + UI.rfb.clipboardPasteDataFrom(data); + UI.needToCheckClipboardChange = false; + }, (err) => { + console.log("No data in clipboard"); + }); + } } }, @@ -1300,15 +1283,13 @@ const UI = { UI.rfb.antiAliasing = UI.getSetting('anti_aliasing'); UI.rfb.clipboardUp = UI.getSetting('clipboard_up'); UI.rfb.clipboardDown = UI.getSetting('clipboard_down'); - UI.rfb.clipboardBinary = supportsBinaryClipboard(); UI.rfb.clipboardSeamless = UI.getSetting('clipboard_seamless'); - if (UI.rfb.clipboardSeamless) { + UI.rfb.clipboardBinary = supportsBinaryClipboard() && UI.rfb.clipboardSeamless; + + //Only explicitly request permission to clipboard on browsers that support binary clipboard access + if (supportsBinaryClipboard()) { // explicitly request permission to the clipboard - if (UI.rfb.clipboardBinary) { - navigator.permissions.query({ name: "clipboard-read" }).then((result) => { console.log('binary clipboard enabled') }); - } else { - navigator.permissions.query({ name: "clipboardRead" }).then((result) => { console.log('binary clipboard enabled') }); - } + navigator.permissions.query({ name: "clipboard-read" }).then((result) => { console.log('binary clipboard enabled') }); } // KASM-960 workaround, disable seamless on Safari if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) diff --git a/core/rfb.js b/core/rfb.js index 98c18616..4e2d7e2b 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -783,21 +783,28 @@ export default class RFB extends EventTargetMixin { clipboardPasteFrom(text) { if (this._rfbConnectionState !== 'connected' || this._viewOnly) { return; } + if (!(typeof text === 'string' && text.length > 0)) { return; } + this.sentEventsCounter+=1; - if (this._clipboardServerCapabilitiesFormats[extendedClipboardFormatText] && - this._clipboardServerCapabilitiesActions[extendedClipboardActionNotify]) { - this._clipboardText = text; - RFB.messages.extendedClipboardNotify(this._sock, [extendedClipboardFormatText]); - } else { - let data = new Uint8Array(text.length); - for (let i = 0; i < text.length; i++) { - // FIXME: text can have values outside of Latin1/Uint8 - data[i] = text.charCodeAt(i); - } - - RFB.messages.clientCutText(this._sock, data); + let data = new Uint8Array(text.length); + for (let i = 0; i < text.length; i++) { + data[i] = text.charCodeAt(i); } + + let h = hashUInt8Array(data); + if (h === this._clipHash) { + console.log('No clipboard changes'); + return; + } else { + this._clipHash = h; + } + + let dataset = []; + let mimes = [ 'text/plain' ]; + dataset.push(data); + + RFB.messages.sendBinaryClipboard(this._sock, dataset, mimes); } async clipboardPasteDataFrom(clipdata) { @@ -816,6 +823,9 @@ export default class RFB extends EventTargetMixin { case 'text/plain': case 'text/html': let blob = await clipdata[i].getType(mime); + if (!blob) { + continue; + } let buff = await blob.arrayBuffer(); let data = new Uint8Array(buff); @@ -2465,31 +2475,48 @@ export default class RFB extends EventTargetMixin { Log.Debug("HandleBinaryClipboard"); let num = this._sock.rQshift8(); // how many different mime types - let blobs = []; - let clipdata = []; let mimes = []; + let clipItemData = {}; console.log('Clipboard items recieved.'); for (let i = 0; i < num; i++) { let mimelen = this._sock.rQshift8(); - const mime = this._sock.rQshiftStr(mimelen); - + let mime = this._sock.rQshiftStr(mimelen); let len = this._sock.rQshift32(); - - const data = this._sock.rQshiftBytes(len); + let data = this._sock.rQshiftBytes(len); switch(mime) { case "image/png": case "text/html": case "text/plain": - if (mimes.includes(mime)){ + //if (mimes.includes(mime)){ + // continue; + //} + mimes.push(mime); + + if (!this.clipboardBinary) { + if (mime == "text/plain") { + + let textdata = new TextDecoder().decode(data); + + if ((textdata.length > 0) && "\0" === textdata.charAt(textdata.length - 1)) { + textdata = textdata.slice(0, -1); + } + + console.log('Clipboard item raw: ' + data); + console.log('Clipboard item decoded: ' + textdata); + this.dispatchEvent(new CustomEvent( + "clipboard", + { detail: { text: textdata } }) + ); + continue; + } continue; } - mimes.push(mime); + console.log("Mime " + mime + ", len ", len); console.log(data); - let blob = new Blob([data], { type: mime }); - clipdata.push(new ClipboardItem({ [mime]: blob })); + clipItemData[mime] = new Blob([data], { type: mime }); break; default: console.log('Mime type skipped: ' + mime); @@ -2497,13 +2524,15 @@ export default class RFB extends EventTargetMixin { } } - if (clipdata.length > 0) { - this._clipHash = 0; - navigator.clipboard.write(clipdata).then( - function() {}, - function(err) { - console.log("Error writing to client clipboard: " + err); - }); + if (Object.keys(clipItemData).length > 0) { + if (this.clipboardBinary) { + this._clipHash = 0; + navigator.clipboard.write([new ClipboardItem(clipItemData)]).then( + function() {}, + function(err) { + console.log("Error writing to client clipboard: " + err); + }); + } } return true; From f3418af9b7b27d3471be4509c8e5764f1cdd66c0 Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 8 Oct 2021 15:08:12 +0000 Subject: [PATCH 08/12] WIP: fix recieving large binary cliipboard --- app/ui.js | 14 ++++---------- core/rfb.js | 33 +++++++++++++++++++++++---------- core/util/browser.js | 2 ++ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/app/ui.js b/app/ui.js index 4803e9e6..c5231555 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1087,20 +1087,12 @@ const UI = { }, clipboardReceive(e) { - if (UI.rfb.clipboardDown && UI.rfb.clipboardSeamless ) { + if (UI.rfb.clipboardDown) { var curvalue = document.getElementById('noVNC_clipboard_text').value; if (curvalue != e.detail.text) { Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0, 40) + "..."); document.getElementById('noVNC_clipboard_text').value = e.detail.text; Log.Debug("<< UI.clipboardReceive"); - if (navigator.clipboard && navigator.clipboard.writeText){ - navigator.clipboard.writeText(e.detail.text) - .then(function () { - //UI.popupMessage("Selection Copied"); - }, function () { - console.error("Failed to write system clipboard (trying to copy from NoVNC clipboard)") - }); - } } } }, @@ -1137,7 +1129,9 @@ const UI = { if (UI.rfb.clipboardBinary) { navigator.clipboard.read().then((data) => { - UI.rfb.clipboardPasteDataFrom(data); + if (UI.rfb) { + UI.rfb.clipboardPasteDataFrom(data); + } UI.needToCheckClipboardChange = false; }, (err) => { console.log("No data in clipboard"); diff --git a/core/rfb.js b/core/rfb.js index 4e2d7e2b..80746fb1 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2474,27 +2474,40 @@ export default class RFB extends EventTargetMixin { _handleBinaryClipboard() { Log.Debug("HandleBinaryClipboard"); + if (this._sock.rQwait("Binary Clipboard header", 2, 1)) { return false; } + let num = this._sock.rQshift8(); // how many different mime types let mimes = []; let clipItemData = {}; - console.log('Clipboard items recieved.'); + let buffByteLen = 2; + console.log(num + ' Clipboard items recieved.'); + console.log('Client sockjs buffer size ' + this._sock.rQlen); + + for (let i = 0; i < num; i++) { + if (this._sock.rQwait("Binary Clipboard mimelen", 1, buffByteLen)) { return false; } + buffByteLen++; let mimelen = this._sock.rQshift8(); + + if (this._sock.rQwait("Binary Clipboard mime", Math.abs(mimelen), buffByteLen)) { return false; } + buffByteLen+=mimelen; let mime = this._sock.rQshiftStr(mimelen); + + if (this._sock.rQwait("Binary Clipboard data len", 4, buffByteLen)) { return false; } + buffByteLen+=4; let len = this._sock.rQshift32(); + + if (this._sock.rQwait("Binary Clipboard data", Math.abs(len), buffByteLen)) { return false; } let data = this._sock.rQshiftBytes(len); + buffByteLen+=len; switch(mime) { case "image/png": case "text/html": case "text/plain": - //if (mimes.includes(mime)){ - // continue; - //} mimes.push(mime); - if (!this.clipboardBinary) { if (mime == "text/plain") { let textdata = new TextDecoder().decode(data); @@ -2509,10 +2522,9 @@ export default class RFB extends EventTargetMixin { "clipboard", { detail: { text: textdata } }) ); - continue; } - continue; - } + + if (!this.clipboardBinary) { continue; } console.log("Mime " + mime + ", len ", len); console.log(data); @@ -2524,6 +2536,8 @@ export default class RFB extends EventTargetMixin { } } + console.log('Client sockjs buffer size ' + this._sock.rQlen); + if (Object.keys(clipItemData).length > 0) { if (this.clipboardBinary) { this._clipHash = 0; @@ -2679,8 +2693,7 @@ export default class RFB extends EventTargetMixin { return true; case 180: // KASM binary clipboard - this._handleBinaryClipboard(); - return true; + return this._handleBinaryClipboard(); case 248: // ServerFence return this._handleServerFenceMsg(); diff --git a/core/util/browser.js b/core/util/browser.js index 95a7ada7..0bff0d4d 100644 --- a/core/util/browser.js +++ b/core/util/browser.js @@ -102,6 +102,8 @@ export function isFirefox() { } export function supportsBinaryClipboard() { + //Safari does support the clipbaord API but has a lot of security restrictions + if (isSafari()) { return false; } return (typeof navigator.clipboard.read === "function"); } From f8fde81dfb9cc95f275642690954636865e648bf Mon Sep 17 00:00:00 2001 From: matt Date: Tue, 12 Oct 2021 14:11:26 +0000 Subject: [PATCH 09/12] WIP: cleanup code --- app/ui.js | 6 +++--- core/rfb.js | 35 +++++++++++++++++------------------ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/ui.js b/app/ui.js index c5231555..6e13cf0e 100644 --- a/app/ui.js +++ b/app/ui.js @@ -1122,7 +1122,7 @@ const UI = { }, copyFromLocalClipboard: function copyFromLocalClipboard() { if (!document.hasFocus()) { - console.log("window does not have focus"); + Log.Debug("window does not have focus"); return; } if (UI.rfb && UI.rfb.clipboardUp && UI.rfb.clipboardSeamless) { @@ -1134,7 +1134,7 @@ const UI = { } UI.needToCheckClipboardChange = false; }, (err) => { - console.log("No data in clipboard"); + Log.Debug("No data in clipboard"); }); } } @@ -1283,7 +1283,7 @@ const UI = { //Only explicitly request permission to clipboard on browsers that support binary clipboard access if (supportsBinaryClipboard()) { // explicitly request permission to the clipboard - navigator.permissions.query({ name: "clipboard-read" }).then((result) => { console.log('binary clipboard enabled') }); + navigator.permissions.query({ name: "clipboard-read" }).then((result) => { Log.Debug('binary clipboard enabled') }); } // KASM-960 workaround, disable seamless on Safari if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) diff --git a/core/rfb.js b/core/rfb.js index 80746fb1..3e9a6c0c 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -794,7 +794,7 @@ export default class RFB extends EventTargetMixin { let h = hashUInt8Array(data); if (h === this._clipHash) { - console.log('No clipboard changes'); + Log.Debug('No clipboard changes'); return; } else { this._clipHash = h; @@ -832,7 +832,7 @@ export default class RFB extends EventTargetMixin { if (!h) { h = hashUInt8Array(data); if (h === this._clipHash) { - console.log('No clipboard changes'); + Log.Debug('No clipboard changes'); return; } else { this._clipHash = h; @@ -845,10 +845,10 @@ export default class RFB extends EventTargetMixin { mimes.push(mime); dataset.push(data); - console.log('Sending mime type: ' + mime); + Log.Debug('Sending mime type: ' + mime); break; default: - console.log('skipping clip send mime type: ' + mime) + Log.Debug('skipping clip send mime type: ' + mime) } } @@ -1073,7 +1073,7 @@ export default class RFB extends EventTargetMixin { try { if (x > 1280 && limited && this.videoQuality == 1) { var ratio = y / x; - console.log(ratio); + Log.Debug(ratio); x = 1280; y = x * ratio; } @@ -1082,7 +1082,7 @@ export default class RFB extends EventTargetMixin { y = 720; } } catch (err) { - console.log(err); + Log.Debug(err); } return { w: x, @@ -2480,8 +2480,8 @@ export default class RFB extends EventTargetMixin { let mimes = []; let clipItemData = {}; let buffByteLen = 2; - console.log(num + ' Clipboard items recieved.'); - console.log('Client sockjs buffer size ' + this._sock.rQlen); + Log.Debug(num + ' Clipboard items recieved.'); + Log.Debug('Started clipbooard processing with Client sockjs buffer size ' + this._sock.rQlen); @@ -2516,8 +2516,7 @@ export default class RFB extends EventTargetMixin { textdata = textdata.slice(0, -1); } - console.log('Clipboard item raw: ' + data); - console.log('Clipboard item decoded: ' + textdata); + Log.Debug("Plain text clipboard recieved and placed in text element, size: " + textdata.length); this.dispatchEvent(new CustomEvent( "clipboard", { detail: { text: textdata } }) @@ -2526,17 +2525,17 @@ export default class RFB extends EventTargetMixin { if (!this.clipboardBinary) { continue; } - console.log("Mime " + mime + ", len ", len); - console.log(data); + Log.Debug("Processed binary clipboard of MIME " + mime + " of length " + len); + clipItemData[mime] = new Blob([data], { type: mime }); break; default: - console.log('Mime type skipped: ' + mime); + Log.Debug('Mime type skipped: ' + mime); break; } } - console.log('Client sockjs buffer size ' + this._sock.rQlen); + Log.Debug('Finished processing binary clipboard with client sockjs buffer size ' + this._sock.rQlen); if (Object.keys(clipItemData).length > 0) { if (this.clipboardBinary) { @@ -2544,7 +2543,7 @@ export default class RFB extends EventTargetMixin { navigator.clipboard.write([new ClipboardItem(clipItemData)]).then( function() {}, function(err) { - console.log("Error writing to client clipboard: " + err); + Log.Debug("Error writing to client clipboard: " + err); }); } } @@ -2559,8 +2558,8 @@ export default class RFB extends EventTargetMixin { const text = this._sock.rQshiftStr(length); - console.log("Received KASM bottleneck stats:"); - console.log(text); + Log.Debug("Received KASM bottleneck stats:"); + Log.Debug(text); this.dispatchEvent(new CustomEvent( "bottleneck_stats", { detail: { text: text } })); @@ -3373,7 +3372,7 @@ RFB.messages = { let length = data.length; - console.log('Clipboard data sent mime type ' + mime + ' len ' + length); + Log.Debug('Clipboard data sent mime type ' + mime + ' len ' + length); buff[offset++] = length >> 24; buff[offset++] = length >> 16; From 075bb31d5bf10950bd84c554aa35be5d298c4de9 Mon Sep 17 00:00:00 2001 From: Mariusz Marciniak Date: Wed, 13 Oct 2021 15:22:32 +0200 Subject: [PATCH 10/12] Fix webpack to allow async promises --- app/ui.js | 2 ++ package-lock.json | 54 +++++++++++++++++++++++++++++------------------ package.json | 19 +++++++---------- 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/app/ui.js b/app/ui.js index 092519e8..9cc254ad 100644 --- a/app/ui.js +++ b/app/ui.js @@ -30,6 +30,8 @@ window.updateSetting = (name, value) => { } } +import "core-js/stable"; +import "regenerator-runtime/runtime"; import * as Log from '../core/util/logging.js'; import _, { l10n } from './localization.js'; import { isTouchDevice, isSafari, hasScrollbarGutter, dragThreshold, supportsBinaryClipboard, isFirefox } diff --git a/package-lock.json b/package-lock.json index 3f57a171..bc278c5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "chai": "*", "clean-webpack-plugin": "^3.0.0", "commander": "*", - "core-js": "*", + "core-js": "^3.18.3", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^1.1.5", "es-module-loader": "*", @@ -48,6 +48,7 @@ "po2json": "*", "postcss-loader": "^4.1.0", "preload-webpack-plugin": "^3.0.0-beta.4", + "regenerator-runtime": "^0.13.9", "requirejs": "*", "rollup": "*", "rollup-plugin-node-resolve": "*", @@ -4010,9 +4011,9 @@ } }, "node_modules/core-js": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.2.tgz", - "integrity": "sha512-P0KPukO6OjMpjBtHSceAZEWlDD1M2Cpzpg6dBbrjFqFhBHe/BwhxaP820xKOjRn/lZRQirrCusIpLS/n2sgXLQ==", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz", + "integrity": "sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw==", "dev": true, "hasInstallScript": true, "funding": { @@ -18310,7 +18311,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-node": { "version": "1.8.2", @@ -18364,13 +18366,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "alphanum-sort": { "version": "1.0.2", @@ -18684,7 +18688,8 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-10.0.0.tgz", "integrity": "sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==", - "dev": true + "dev": true, + "requires": {} }, "babylon": { "version": "6.18.0", @@ -19806,9 +19811,9 @@ "dev": true }, "core-js": { - "version": "3.16.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.16.2.tgz", - "integrity": "sha512-P0KPukO6OjMpjBtHSceAZEWlDD1M2Cpzpg6dBbrjFqFhBHe/BwhxaP820xKOjRn/lZRQirrCusIpLS/n2sgXLQ==", + "version": "3.18.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz", + "integrity": "sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw==", "dev": true }, "core-js-compat": { @@ -20819,7 +20824,8 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -22436,7 +22442,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -23347,19 +23354,22 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", - "dev": true + "dev": true, + "requires": {} }, "karma-script-launcher": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/karma-script-launcher/-/karma-script-launcher-1.0.0.tgz", "integrity": "sha1-zQF8TeXvCeWp2nkydhdhCN1LVC0=", - "dev": true + "dev": true, + "requires": {} }, "karma-sinon-chai": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/karma-sinon-chai/-/karma-sinon-chai-2.0.2.tgz", "integrity": "sha512-SDgh6V0CUd+7ruL1d3yG6lFzmJNGRNQuEuCYXLaorruNP9nwQfA7hpsp4clx4CbOo5Gsajh3qUOT7CrVStUKMw==", - "dev": true + "dev": true, + "requires": {} }, "kew": { "version": "0.7.0", @@ -25647,7 +25657,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -26312,7 +26323,8 @@ "version": "3.0.0-beta.4", "resolved": "https://registry.npmjs.org/preload-webpack-plugin/-/preload-webpack-plugin-3.0.0-beta.4.tgz", "integrity": "sha512-6hhh0AswCbp/U4EPVN4fbK2wiDkXhmgjjgEYEmXa21UYwjYzCIgh3ZRMXM21ZPLfbQGpdFuSL3zFslU+edjpwg==", - "dev": true + "dev": true, + "requires": {} }, "prelude-ls": { "version": "1.2.1", @@ -27246,7 +27258,8 @@ "version": "2.14.0", "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-2.14.0.tgz", "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", - "dev": true + "dev": true, + "requires": {} }, "slash": { "version": "2.0.0", @@ -29645,7 +29658,8 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.0.tgz", "integrity": "sha512-uYhVJ/m9oXwEI04iIVmgLmugh2qrZihkywG9y5FfZV2ATeLIzHf93qs+tUNqlttbQK957/VX3mtwAS+UfIwA4g==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 06b07997..5cee3929 100644 --- a/package.json +++ b/package.json @@ -30,29 +30,27 @@ "url": "git+https://github.com/kasmtech/noVNC.git" }, "author": "Kasm Technologies (https://www.kasmweb.com)", - "contributors": [ - ], + "contributors": [], "license": "MPL-2.0", "bugs": { "url": "https://github.com/kasmtech/noVNC/issues" }, "homepage": "https://github.com/kasmtech/noVNC", "devDependencies": { - "@babel/core": "^7.12.10", - "@babel/preset-env": "^7.12.11", + "@babel/cli": "*", "@babel/core": "*", - "babel-loader": "^8.2.2", "@babel/plugin-syntax-dynamic-import": "*", "@babel/plugin-transform-modules-commonjs": "*", "@babel/preset-env": "*", - "@babel/cli": "*", + "@chiragrupani/karma-chromium-edge-launcher": "*", + "babel-loader": "^8.2.2", "babel-plugin-import-redirect": "*", - "browserify": "*", "babelify": "*", - "core-js": "*", + "browserify": "*", "chai": "*", "clean-webpack-plugin": "^3.0.0", "commander": "*", + "core-js": "^3.18.3", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^1.1.5", "es-module-loader": "*", @@ -64,11 +62,10 @@ "html-webpack-plugin": "^4.5.0", "jsdom": "*", "karma": "*", - "karma-mocha": "*", "karma-chrome-launcher": "*", - "@chiragrupani/karma-chromium-edge-launcher": "*", "karma-firefox-launcher": "*", "karma-ie-launcher": "*", + "karma-mocha": "*", "karma-mocha-reporter": "*", "karma-safari-launcher": "*", "karma-script-launcher": "*", @@ -79,6 +76,7 @@ "po2json": "*", "postcss-loader": "^4.1.0", "preload-webpack-plugin": "^3.0.0-beta.4", + "regenerator-runtime": "^0.13.9", "requirejs": "*", "rollup": "*", "rollup-plugin-node-resolve": "*", @@ -90,7 +88,6 @@ "webpack": "^4.29.6", "webpack-cli": "^3.2.3" }, - "dependencies": {}, "keywords": [ "vnc", "rfb", From 37c41c60f604a7cb25592b433ccf02c1cbc3cfc5 Mon Sep 17 00:00:00 2001 From: matt Date: Thu, 14 Oct 2021 17:33:03 +0000 Subject: [PATCH 11/12] Fixes for older Chrome browsers --- core/rfb.js | 25 ++++++++++++++++++------- core/util/browser.js | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/core/rfb.js b/core/rfb.js index b5b4a962..101345ed 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -834,7 +834,7 @@ export default class RFB extends EventTargetMixin { Log.Debug('Sending mime type: ' + mime); break; default: - Log.Debug('skipping clip send mime type: ' + mime) + Log.Info('skipping clip send mime type: ' + mime) } } @@ -2471,7 +2471,8 @@ export default class RFB extends EventTargetMixin { let mimes = []; let clipItemData = {}; let buffByteLen = 2; - Log.Debug(num + ' Clipboard items recieved.'); + let textdata = ''; + Log.Info(num + ' Clipboard items recieved.'); Log.Debug('Started clipbooard processing with Client sockjs buffer size ' + this._sock.rQlen); @@ -2501,7 +2502,7 @@ export default class RFB extends EventTargetMixin { if (mime == "text/plain") { - let textdata = new TextDecoder().decode(data); + textdata = new TextDecoder().decode(data); if ((textdata.length > 0) && "\0" === textdata.charAt(textdata.length - 1)) { textdata = textdata.slice(0, -1); @@ -2516,7 +2517,7 @@ export default class RFB extends EventTargetMixin { if (!this.clipboardBinary) { continue; } - Log.Debug("Processed binary clipboard of MIME " + mime + " of length " + len); + Log.Info("Processed binary clipboard of MIME " + mime + " of length " + len); clipItemData[mime] = new Blob([data], { type: mime }); break; @@ -2534,8 +2535,18 @@ export default class RFB extends EventTargetMixin { navigator.clipboard.write([new ClipboardItem(clipItemData)]).then( function() {}, function(err) { - Log.Debug("Error writing to client clipboard: " + err); - }); + Log.Error("Error writing to client clipboard: " + err); + // Lets try writeText + if (textdata.length > 0) { + navigator.clipboard.writeText(textdata).then( + function() {}, + function(err2) { + Log.Error("Error writing text to client clipboard: " + err2); + } + ); + } + } + ); } } @@ -3369,7 +3380,7 @@ RFB.messages = { let length = data.length; - Log.Debug('Clipboard data sent mime type ' + mime + ' len ' + length); + Log.Info('Clipboard data sent mime type ' + mime + ' len ' + length); buff[offset++] = length >> 24; buff[offset++] = length >> 16; diff --git a/core/util/browser.js b/core/util/browser.js index 0bff0d4d..39ca4468 100644 --- a/core/util/browser.js +++ b/core/util/browser.js @@ -104,6 +104,6 @@ export function isFirefox() { export function supportsBinaryClipboard() { //Safari does support the clipbaord API but has a lot of security restrictions if (isSafari()) { return false; } - return (typeof navigator.clipboard.read === "function"); + return (navigator.clipboard && typeof navigator.clipboard.read === "function"); } From a36f941493e99338da6b14084047788ab0490ced Mon Sep 17 00:00:00 2001 From: matt Date: Fri, 15 Oct 2021 19:34:01 +0000 Subject: [PATCH 12/12] fix Edge 18 and under clipboard --- core/rfb.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/rfb.js b/core/rfb.js index 101345ed..ba1d72f8 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -2502,7 +2502,10 @@ export default class RFB extends EventTargetMixin { if (mime == "text/plain") { - textdata = new TextDecoder().decode(data); + //textdata = new TextDecoder().decode(data); + for (let i = 0; i < data.length; i++) { + textdata+=String.fromCharCode(data[i]); + } if ((textdata.length > 0) && "\0" === textdata.charAt(textdata.length - 1)) { textdata = textdata.slice(0, -1);