Fork
This commit is contained in:
parent
924a8f1350
commit
61c0a11524
87
README.md
87
README.md
|
|
@ -1,12 +1,85 @@
|
||||||
#Clipboard Inserter
|
# Native Inserter
|
||||||
|
|
||||||
A simple addon whose purpose is to automatically insert contents of clipboard into the page.
|
Fork of the Clipboard Inserter whose purpose it is to automatically insert the
|
||||||
|
data received by an in this repository included native application into a
|
||||||
|
broswer page.
|
||||||
|
|
||||||
If you're looking to install this addon, it's available here:
|
It is useful for use with text extraction tools like Textractor for looking up
|
||||||
|
words in the browser.
|
||||||
|
|
||||||
- [Firefox](https://addons.mozilla.org/firefox/addon/clipboard-inserter/)
|
This repository contains the browser plugin, native application and native
|
||||||
- [Chrome web store](https://chrome.google.com/webstore/detail/clipboard-inserter/deahejllghicakhplliloeheabddjajm)
|
manifest. Textractor plugin is not included.
|
||||||
|
|
||||||
Or, if the links don't work, just search for "clipboard inserter" in the appropriate addon market.
|
Currently this addon is not available on any of the browsers Addon Store.
|
||||||
|
|
||||||
On Chrome, if you want to use this extension with local files (the `file://` protocol), you have to enable the "Allow access to file URLs" setting in the extension details.
|
Only tested on GNU/Linux with Firefox.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
You will need:
|
||||||
|
|
||||||
|
- This code
|
||||||
|
- A go compiler
|
||||||
|
- A browser supporting native extensions (eg modern Firefox, Chrome)
|
||||||
|
|
||||||
|
1) Compile and install the native application
|
||||||
|
2) Put the native application manifest into your browsers expected directory
|
||||||
|
3) Install the addon in your browser
|
||||||
|
|
||||||
|
For this to be useful you will also need a sender, for example a Textractor
|
||||||
|
plugin (not included) and a browser page that the can be inserted to.
|
||||||
|
|
||||||
|
## How does it work
|
||||||
|
|
||||||
|
The browser plugin starts a native application which creates a raw TCP listen
|
||||||
|
socket for incoming connections.
|
||||||
|
|
||||||
|
One or more applications (eg Textractor plugin) will connect to this socket and
|
||||||
|
send messages. Messages consist of a 4 Byte little-endian length header and
|
||||||
|
UTF-8 payload.
|
||||||
|
|
||||||
|
The native application will forward the UTF-8 data to the browser plugin which
|
||||||
|
in turn will insert the text into a webpage similar to the original clipboard
|
||||||
|
version.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
The native application is writing to stderr. Browsers usually forward this to
|
||||||
|
their default log.
|
||||||
|
|
||||||
|
The browser addon itself
|
||||||
|
|
||||||
|
## (Not so) FAQ
|
||||||
|
|
||||||
|
#### Why not the clipboard
|
||||||
|
|
||||||
|
Abusing the clipboard functionality to transfer data to the browser is ugly. It
|
||||||
|
does also not work under Wayland which does not allow unfocused applications to
|
||||||
|
access the clipboard.
|
||||||
|
|
||||||
|
(No opinion here whether the native application/messaging setup is more or less
|
||||||
|
ugly than the clipboard approach)
|
||||||
|
|
||||||
|
It also has the bonus of working with VMs without setting up clipboard
|
||||||
|
forwarding between host and guest.
|
||||||
|
|
||||||
|
#### Why a native application
|
||||||
|
|
||||||
|
The APIs including TCP listen sockets are no longer supported by modern
|
||||||
|
Browsers. The only alternative would be connecting to a WebSocket server, but
|
||||||
|
this has two drawbacks:
|
||||||
|
|
||||||
|
1) The API only supports the client side protocol and does not implement a
|
||||||
|
server. This would mean the sending side would need to be the server and the
|
||||||
|
native application the client, which would not work with multiple senders.
|
||||||
|
|
||||||
|
2) The sending side (eg C++ Textractor plugin) will need to implement a
|
||||||
|
Websocket server which is a lot more work than raw tcp.
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
- Native messaging requires native host order prefixed messages. Golang does
|
||||||
|
not have an easy way to retrieve this information. The native application
|
||||||
|
will use little-endian which is the default on most common architectures
|
||||||
|
- Messages over the native-messaging 1MB limit will be discarded instead of
|
||||||
|
split up
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
<script src="/default-options.js"></script>
|
<script src="/default-options.js"></script>
|
||||||
<script src="monitor.js"></script>
|
<script src="monitor.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
console.log("I'm alive")
|
console.log("I'm alive")
|
||||||
|
|
||||||
let previousContent = ""
|
|
||||||
let listeningTabs = []
|
let listeningTabs = []
|
||||||
let timer = null
|
|
||||||
let options = defaultOptions
|
let options = defaultOptions
|
||||||
|
|
||||||
chrome.storage.local.get(defaultOptions,
|
chrome.storage.local.get(defaultOptions,
|
||||||
o => options = o)
|
o => options = o)
|
||||||
|
|
||||||
|
|
||||||
chrome.storage.onChanged.addListener((changes, area) => {
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
if(area === "local") {
|
if(area === "local") {
|
||||||
const optionKeys = Object.keys(options)
|
const optionKeys = Object.keys(options)
|
||||||
|
|
@ -16,78 +15,45 @@ chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
options[key] = changes[key].newValue
|
options[key] = changes[key].newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateTimer()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
chrome.browserAction.onClicked.addListener(() => {
|
chrome.browserAction.onClicked.addListener((tab) => {
|
||||||
chrome.tabs.query({ active: true, currentWindow: true },
|
toggleTab(tab.id)
|
||||||
([t]) => toggleTab(t.id))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.onload = () => {
|
napp = browser.runtime.connectNative("native_inserter")
|
||||||
document.querySelector("#paste-target").addEventListener("paste", e => {
|
napp.onDisconnect.addListener((p) => {
|
||||||
if(e.clipboardData.getData("text/plain") === "") {
|
if (p.error) {
|
||||||
e.preventDefault() // prevent anything that is not representable as plain text from being pasted
|
console.error(`Disconnected native app due to an error: ${p.error.message}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
napp.onMessage.addListener((msg) => {
|
||||||
|
console.log("Received: " + msg.body)
|
||||||
|
const pasteTarget = document.querySelector("#paste-target")
|
||||||
|
pasteTarget.innerText = msg.body
|
||||||
|
const content = pasteTarget.innerText
|
||||||
|
listeningTabs.forEach(id => notifyForeground(id, content))
|
||||||
|
})
|
||||||
|
|
||||||
function toggleTab(id) {
|
function toggleTab(id) {
|
||||||
const index = listeningTabs.indexOf(id)
|
const index = listeningTabs.indexOf(id)
|
||||||
if(index >= 0) {
|
if(index >= 0) {
|
||||||
uninject(id)
|
uninject(id)
|
||||||
listeningTabs.splice(index, 1)
|
listeningTabs.splice(index, 1)
|
||||||
updateTimer()
|
|
||||||
chrome.browserAction.setBadgeText({ text: "", tabId: id })
|
chrome.browserAction.setBadgeText({ text: "", tabId: id })
|
||||||
} else {
|
} else {
|
||||||
chrome.tabs.executeScript({file: "/fg/insert.js"})
|
chrome.tabs.executeScript({file: "/fg/insert.js"})
|
||||||
listeningTabs.push(id)
|
listeningTabs.push(id)
|
||||||
updateTimer()
|
|
||||||
chrome.browserAction.setBadgeBackgroundColor({ color: "green", tabId: id })
|
chrome.browserAction.setBadgeBackgroundColor({ color: "green", tabId: id })
|
||||||
chrome.browserAction.setBadgeText({ text: "ON", tabId: id })
|
chrome.browserAction.setBadgeText({ text: "ON", tabId: id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyForeground(id, text) {
|
function notifyForeground(id, text) {
|
||||||
chrome.tabs.sendMessage(id, {
|
chrome.tabs.sendMessage(id, { action: "insert", text, options })
|
||||||
action: "insert", text, options
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function uninject(id) {
|
function uninject(id) {
|
||||||
chrome.tabs.sendMessage(id, { action: "uninject" })
|
chrome.tabs.sendMessage(id, { action: "uninject" })
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkClipboard() {
|
|
||||||
const pasteTarget = document.querySelector("#paste-target")
|
|
||||||
pasteTarget.innerText = ""
|
|
||||||
pasteTarget.focus()
|
|
||||||
document.execCommand("paste")
|
|
||||||
const content = pasteTarget.innerText
|
|
||||||
if(content.trim() !== previousContent.trim() && content != "") {
|
|
||||||
listeningTabs.forEach(id => notifyForeground(id, content))
|
|
||||||
previousContent = content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTimer() {
|
|
||||||
function stop() {
|
|
||||||
clearInterval(timer.id)
|
|
||||||
timer = null
|
|
||||||
}
|
|
||||||
function start() {
|
|
||||||
const id = setInterval(checkClipboard, options.monitorInterval)
|
|
||||||
timer = { id, interval: options.monitorInterval }
|
|
||||||
}
|
|
||||||
if(listeningTabs.length > 0) {
|
|
||||||
if(timer === null) {
|
|
||||||
start()
|
|
||||||
} else if(timer.interval !== options.monitorInterval) {
|
|
||||||
stop()
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Clipboard Inserter",
|
"name": "Native Inserter",
|
||||||
"version": "0.3.2",
|
"version": "1.0.0",
|
||||||
|
|
||||||
"description": "A simple addon that inserts clipboard contents into the page. Uses icon made by Google from www.flaticon.com licensed by CC 3.0 BY",
|
"description": "An addon that inserts contents received from a native application into a page. Forked from clipboard-inserter. Uses icon made by Google from www.flaticon.com licensed by CC 3.0 BY",
|
||||||
|
|
||||||
"icons": {},
|
"icons": {},
|
||||||
|
|
||||||
"applications": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "@clipboard-inserter"
|
"id": "@native-inserter",
|
||||||
|
"strict_min_version": "50.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"clipboardRead",
|
|
||||||
"storage",
|
"storage",
|
||||||
|
"nativeMessaging",
|
||||||
"file://*/*"
|
"file://*/*"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@
|
||||||
"32": "icon/icon32.png",
|
"32": "icon/icon32.png",
|
||||||
"64": "icon/icon64.png"
|
"64": "icon/icon64.png"
|
||||||
},
|
},
|
||||||
"default_title": "Toggle clipboard inserter"
|
"default_title": "Toggle Native Inserter"
|
||||||
},
|
},
|
||||||
|
|
||||||
"background": {
|
"background": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue