Skip to content

Commit

Permalink
Add automatic clipboard support
Browse files Browse the repository at this point in the history
  • Loading branch information
juanjoDiaz committed Dec 19, 2019
1 parent 84a8c1b commit e6b643c
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 6 deletions.
46 changes: 46 additions & 0 deletions core/clipboard.js
Original file line number Diff line number Diff line change
@@ -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;
24 changes: 19 additions & 5 deletions core/rfb.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { decodeUTF8 } from './util/strings.js';
import { dragThreshold } from './util/browser.js';
import EventTargetMixin from './util/eventtarget.js';
import Display from "./display.js";
import Clipboard from "./clipboard.js";
import Keyboard from "./input/keyboard.js";
import Mouse from "./input/mouse.js";
import Cursor from "./util/cursor.js";
Expand Down Expand Up @@ -88,6 +89,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._mouse = null; // Mouse input handler object

Expand Down Expand Up @@ -173,6 +175,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);

Expand Down Expand Up @@ -264,9 +269,11 @@ export default class RFB extends EventTargetMixin {
if (viewOnly) {
this._keyboard.ungrab();
this._mouse.ungrab();
this._clipboard.ungrab();
} else {
this._keyboard.grab();
this._mouse.grab();
this._clipboard.grab();
}
}
}
Expand Down Expand Up @@ -1227,8 +1234,11 @@ export default class RFB extends EventTargetMixin {
this._setDesktopName(name);
this._resize(width, height);

if (!this._viewOnly) { this._keyboard.grab(); }
if (!this._viewOnly) { this._mouse.grab(); }
if (!this._viewOnly) {
this._keyboard.grab();
this._mouse.grab();
this._clipboard.grab();
}

this._fb_depth = 24;

Expand Down Expand Up @@ -1337,9 +1347,13 @@ export default class RFB extends EventTargetMixin {

if (this._viewOnly) { 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 }));
}

return true;
}
Expand Down
24 changes: 23 additions & 1 deletion docs/API-internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ 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.


## 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.
Expand Down Expand Up @@ -118,3 +120,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 changes: 54 additions & 0 deletions tests/test.clipboard.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
});
});

0 comments on commit e6b643c

Please sign in to comment.