Add automatic clipboard support

This commit is contained in:
Juanjo Diaz 2019-12-18 15:24:03 +02:00
parent 9985950bfa
commit dc41145770
4 changed files with 141 additions and 5 deletions

46
core/clipboard.js Normal file
View File

@ -0,0 +1,46 @@
export default class Clipboard {
constructor(target) {
this._target = target;
this._eventHandlers = {
'copy': this._handleCopy.bind(this),
'paste': this._handlePaste.bind(this)
};
// ===== EVENT HANDLERS =====
this.onpaste = () => {};
}
// ===== PRIVATE METHODS =====
_handleCopy(e) {
if (navigator.clipboard.writeText) {
navigator.clipboard.writeText(e.clipboardData.getData('text/plain')).catch(() => {/* Do nothing */});
}
}
_handlePaste(e) {
if (navigator.clipboard.readText) {
navigator.clipboard.readText().then(this.onpaste).catch(() => {/* Do nothing */});
} else if (e.clipboardData) {
this.onpaste(e.clipboardData.getData('text/plain'));
}
}
// ===== PUBLIC METHODS =====
grab() {
if (!Clipboard.isSupported) return;
this._target.addEventListener('copy', this._eventHandlers.copy);
this._target.addEventListener('paste', this._eventHandlers.paste);
}
ungrab() {
if (!Clipboard.isSupported) return;
this._target.removeEventListener('copy', this._eventHandlers.copy);
this._target.removeEventListener('paste', this._eventHandlers.paste);
}
}
Clipboard.isSupported = (navigator && navigator.clipboard) ? true : false;

View File

@ -15,6 +15,7 @@ import { clientToElement } from './util/element.js';
import { setCapture } from './util/events.js';
import EventTargetMixin from './util/eventtarget.js';
import Display from "./display.js";
import Clipboard from "./clipboard.js";
import Inflator from "./inflator.js";
import Deflator from "./deflator.js";
import Keyboard from "./input/keyboard.js";
@ -158,6 +159,7 @@ export default class RFB extends EventTargetMixin {
this._sock = null; // Websock object
this._display = null; // Display object
this._flushing = false; // Display flushing state
this._clipboard = null; // Clipboard object
this._keyboard = null; // Keyboard input handler object
this._gestures = null; // Gesture input handler object
this._resizeObserver = null; // Resize observer object
@ -259,6 +261,9 @@ export default class RFB extends EventTargetMixin {
}
this._display.onflush = this._onFlush.bind(this);
this._clipboard = new Clipboard(this._canvas);
this._clipboard.onpaste = this.clipboardPasteFrom.bind(this);
this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
@ -310,8 +315,10 @@ export default class RFB extends EventTargetMixin {
this._rfbConnectionState === "connected") {
if (viewOnly) {
this._keyboard.ungrab();
this._clipboard.ungrab();
} else {
this._keyboard.grab();
this._clipboard.grab();
}
}
}
@ -2062,7 +2069,10 @@ export default class RFB extends EventTargetMixin {
this._setDesktopName(name);
this._resize(width, height);
if (!this._viewOnly) { this._keyboard.grab(); }
if (!this._viewOnly) {
this._keyboard.grab();
this._clipboard.grab();
}
this._fbDepth = 24;
@ -2189,9 +2199,13 @@ export default class RFB extends EventTargetMixin {
return true;
}
this.dispatchEvent(new CustomEvent(
"clipboard",
{ detail: { text: text } }));
this.dispatchEvent(new CustomEvent("clipboard", { detail: { text: text } }));
if (Clipboard.isSupported) {
const clipboardData = new DataTransfer();
clipboardData.setData("text/plain", text);
this._canvas.dispatchEvent(new ClipboardEvent('copy', { clipboardData }));
}
} else {
//Extended msg.

View File

@ -18,6 +18,8 @@ keysym values.
* __Display__ (core/display.js): Efficient 2D rendering abstraction
layered on the HTML5 canvas element.
* __Clipboard__ (core/clipboard.js): Clipboard event handler.
* __Websock__ (core/websock.js): Websock client from websockify
with transparent binary data support.
[Websock API](https://github.com/novnc/websockify-js/wiki/websock.js) wiki page.
@ -25,7 +27,7 @@ with transparent binary data support.
## 1.2 Callbacks
For the Mouse, Keyboard and Display objects the callback functions are
For the Mouse, Keyboard, Display and Clipboard objects the callback functions are
assigned to configuration attributes, just as for the RFB object. The
WebSock module has a method named 'on' that takes two parameters: the
callback event name, and the callback function.
@ -87,3 +89,23 @@ None
| name | parameters | description
| ------- | ---------- | ------------
| onflush | () | A display flush has been requested and we are now ready to resume FBU processing
## 2.4 Clipboard Module
### 2.4.1 Configuration Attributes
None
### 2.4.2 Methods
| name | parameters | description
| ------------------ | ----------------- | ------------
| grab | () | Begin capturing clipboard events
| ungrab | () | Stop capturing clipboard events
### 2.3.3 Callbacks
| name | parameters | description
| ------- | ---------- | ------------
| onpaste | (text) | Called with the text content of the clipboard when the user paste something

54
tests/test.clipboard.js Normal file
View File

@ -0,0 +1,54 @@
const expect = chai.expect;
import Clipboard from '../core/clipboard.js';
describe('Automatic Clipboard Sync', function () {
"use strict";
if (Clipboard.isSupported) {
beforeEach(function () {
if (navigator.clipboard.writeText) {
sinon.spy(navigator.clipboard, 'writeText');
}
if (navigator.clipboard.readText) {
sinon.spy(navigator.clipboard, 'readText');
}
});
afterEach(function () {
if (navigator.clipboard.writeText) {
navigator.clipboard.writeText.restore();
}
if (navigator.clipboard.readText) {
navigator.clipboard.readText.restore();
}
});
}
it('incoming clipboard data from the server is copied to the local clipboard', function () {
const text = 'Random string for testing';
const clipboard = new Clipboard();
if (Clipboard.isSupported) {
const clipboardData = new DataTransfer();
clipboardData.setData("text/plain", text);
clipboard._handleCopy(new ClipboardEvent('paste', { clipboardData }));
if (navigator.clipboard.writeText) {
expect(navigator.clipboard.writeText).to.have.been.calledWith(text);
}
}
});
it('should copy local pasted data to the server clipboard', function () {
const text = 'Another random string for testing';
const clipboard = new Clipboard();
clipboard.onpaste = pasterText => expect(pasterText).to.equal(text);
if (Clipboard.isSupported) {
const clipboardData = new DataTransfer();
clipboardData.setData("text/plain", text);
clipboard._handlePaste(new ClipboardEvent('paste', { clipboardData }));
if (navigator.clipboard.readText) {
expect(navigator.clipboard.readText).to.have.been.called;
}
}
});
});