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