Skip to content

Instantly share code, notes, and snippets.

@LewdEwe-ErikWallace
Last active June 4, 2020 07:46
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save LewdEwe-ErikWallace/d6e36f899a77a4f05e46388c254d014f to your computer and use it in GitHub Desktop.
Save LewdEwe-ErikWallace/d6e36f899a77a4f05e46388c254d014f to your computer and use it in GitHub Desktop.
This is an example of how to poll the clipboard for changes in an Electron application. See notes in HTML.
<!DOCTYPE html>
<html lang="en">
<head>
<title>My App - Clipboard</title>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' chrome-extension://*;">
</head>
<body>
<--
This is an example of how to poll the clipboard for changes in an Electron application.
In this example I'm monitoring the clipboard for the possibility of the user copying a login token -
identified by a prefix - from an email, and then I notify the rest of the app (not shown in this gist)
so the token can be pasted automatically into a login form.
The clipboard.js code creates a hidden Electron window containing this HTML, which is essentially an empty document.
It also does preload injection with preload_clipboard.js, which is what polls the clipboard every 250 ms.
The code is run in a separate window like this so it doesn't load the main Electron-Node process.
However, you could run the polling code anywhere that you can access the require('electron') object.
-->
</body>
</html>
"use strict";
// Import dependencies.
let electron = require('electron');
let emitter = require('events').EventEmitter;
let path = require('path');
let url = require('url');
// Constants.
const LOGIN_TOKEN_PREFIX = 'my-app:login-token#';
/*================================================================================================*/
/*································································································*/
/*
Constructor to create a clipboard instance and initialise its data.
*/
function Clipboard()
{
Object.defineProperty(this, 'window', {value: null, writable: true}); // The clipboard window.
// Setup event handlers for IPC messages.
electron.ipcMain.on('clipboard_message', this.ipc_message_handler.bind(this));
// Startup the database window.
this.create_window();
}
// Set the inheritance prototype of the constructor and name it.
Clipboard.prototype = Object.create(emitter.prototype, {constructor: {value: Clipboard}});
Clipboard.prototype.name = '<Clipboard>';
/*================================================================================================*/
/*································································································*/
/*
Save any data and close window.
*/
Clipboard.prototype.shutdown = function shutdown()
{
// Close the window.
this.window.close();
}
/*································································································*/
/*
Create the clipboard window.
*/
Clipboard.prototype.create_window = function create_window()
{
// Create the browser window.
this.window = new electron.BrowserWindow(
{
width: 0,
height: 0,
show: false,
webPreferences:
{
nodeIntegration: false,
preload: path.join(__dirname, 'preload_injectors', 'preload_clipboard.js'),
contextIsolation: true
}
}
);
// Load the base HTML of the application.
this.window.loadURL(url.format(
{
pathname: path.join(__dirname, 'clipboard', 'clipboard.html'),
protocol: 'file:',
slashes: true
}
));
// Open the Chrome Dev Tools.
// this.window.webContents.openDevTools();
// Setup window event handlers.
this.window.on('closed', this.window_closed.bind(this));
}
/*································································································*/
/*
Cleanup after the window is closed.
*/
Clipboard.prototype.window_closed = function window_closed()
{
// Dereference the window object.
this.window = null;
}
/*································································································*/
/*
Handle asynchronous clipboard messages coming in.
*/
Clipboard.prototype.ipc_message_handler = function ipc_message_handler(event, message)
{
switch(message.action)
{
case 'clipboard_changed':
if(message.value.indexOf(LOGIN_TOKEN_PREFIX) === 0)
{
// Notify rest of application that useful clipboard content was found.
this.emit('clipboard_login_token', message.value.substr(LOGIN_TOKEN_PREFIX.length));
}
break;
}
}
/*================================================================================================*/
module.exports = Clipboard;
"use strict";
let electron = require('electron');
let last_value = null;
/*================================================================================================*/
/*································································································*/
function post_ipc_message(message)
{
electron.ipcRenderer.send('clipboard_message', message);
}
/*································································································*/
/*
Normally the listener would take the form: function listener(event, message){}.
However, because our windows are sandboxed the event parameter is dropped and all we get
is the message parameter, so the listener needs to be: function listener(message){}.
*/
function add_ipc_message_listener(listener)
{
electron.ipcRenderer.on('ipc_message', listener);
}
/*································································································*/
function remove_ipc_message_listener(listener)
{
electron.ipcRenderer.removeListener('ipc_message', listener);
}
/*================================================================================================*/
function check_clipboard_for_changes()
{
let value = electron.clipboard.readText();
if(last_value !== value)clipboard_changed(value);
}
function clipboard_changed(value)
{
last_value = value;
post_ipc_message({action: 'clipboard_changed', value: value});
}
// Get the initial value of the clipboard.
clipboard_changed(electron.clipboard.readText());
// Check for changes at an interval.
setInterval(check_clipboard_for_changes, 250);
@dispeakble
Copy link

what if I copy twice the same content? the event will not trigger. you will need to nullify the clipboard before storing it. also the last_value variable should be null as well. on copy the clipboard contents will change then clear the clipboard. on paste get the stored clipboard contents apply and paste but clear again.

Still this is not a 100% certified clipboard change event.

@jinx1281255
Copy link

Hi LewdEwe-Eric Wallace --
When I'm running this, I'm getting the following:

"App threw an error during load
_TypeError: Cannot read property 'send' of undefined
at post_ipc_message (/Users/i859050/Documents/SmartStore/preload_clipboard.js:8:26)
at clipboard_changed (/Users/i859050/Documents/SmartStore/preload_clipboard.js:36:3)
at Object. (/Users/i859050/Documents/SmartStore/preload_clipboard.js:40:1)
at Object. (/Users/i859050/Documents/SmartStore/preload_clipboard.js:45:3)
at Module._compile (internal/modules/cjs/loader.js:693:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:704:10)
at Module.load (internal/modules/cjs/loader.js:602:32)
at tryModuleLoad (internal/modules/cjs/loader.js:541:12)
at Function.Module.load (internal/modules/cjs/loader.js:533:3)
at Module.require (internal/modules/cjs/loader.js:640:17)"

It looks like it is referring to this specific area of the code:

function post_ipc_message(message)
{
electron.ipcRenderer.send('clipboard_message', message);
}

I am wondering if you might have any insight on why it would be throwing this exception.

Thanks for your help!
Robin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment