# Accessing clipboard in Perspective

**URL:** <https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741>\
**Category:** Ignition\
**Tags:** perspective\
**Created:** [January 10, 2025, 12:39am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741 "2025-01-10T00:39:53Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 12:39am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/1 "2025-01-10T00:39:53Z")

</div>

Bringing this topic to the surface.  
I have the same usecase, where I need user to upload a screenshot to a database.  
Instead of saving the screenshot, and using File Upload component, to select the saved screenshot.

It will really bump up efficiency if user can do a 'Ctrl+v' on a Text Area, similar to what would you do in Chat interface..

Can you guys remind me, what does it take to do this?

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [January 10, 2025, 12:59am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/2 "2025-01-10T00:59:49Z")

</div>

In any supported manner, the requirement is "a custom module".

In hacky unsupported ways, JS injection. @victordcq might have something readily available.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 1:47am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/3 "2025-01-10T01:47:45Z")

</div>

What skill set do I need to have to create a custom module?  
Any direction where do I start?

Meanwhile, hoping @victordcq has an idea..

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 1:52am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/4 "2025-01-10T01:52:04Z")

</div>

I’ll throw this in here:

> **[system.perspective.runJavaScriptAsync | Musson Industrial](https://docs.mussonindustrial.com/ignition/embr-periscope/scripting/perspective/system-perspective-runJavaScriptAsync/)**
>
> The system.perspective.runJavaScriptAsync function allows you to asynchronously run JavaScript code on the client side within a Perspective session.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 2:32am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/5 "2025-01-10T02:32:39Z")

</div>

so the intention is to use javascript, to extract images bytes from clipboard via 'Ctrl+V' (or a button click) and pass the bytes to perspective?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 2:40am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/6 "2025-01-10T02:40:28Z")

</div>

Exactly.

Here's a simple example, reading the clipboard as text.

```python
# Get Clipboard Button
def runAction(self, event):
	
	def withClipboard(clipboard):
		self.view.custom.clipboard = clipboard
		
	
	system.perspective.runJavaScriptAsync('''async () => {
		return await navigator.clipboard.readText()
	}''', {}, withClipboard)

```

> **View JSON**
>
> ```json
> {
> "custom": {
> "clipboard": ""
> },
> "params": {},
> "propConfig": {
> "custom.clipboard": {
> "persistent": true
> }
> },
> "props": {},
> "root": {
> "children": [
> {
> "events": {
> "dom": {
> "onClick": {
> "config": {
> "script": "\t\n\tdef withClipboard(clipboard):\n\t\tself.view.custom.clipboard \u003d clipboard\n\t\t\n\t\n\tsystem.perspective.runJavaScriptAsync(\u0027\u0027\u0027async () \u003d\u003e {\n\t\treturn await navigator.clipboard.readText()\n\t}\u0027\u0027\u0027, {}, withClipboard)"
> },
> "scope": "G",
> "type": "script"
> }
> }
> },
> "meta": {
> "name": "Button"
> },
> "position": {
> "height": 34,
> "width": 193,
> "x": 499,
> "y": 167
> },
> "props": {
> "text": "Get Clipboard"
> },
> "type": "ia.input.button"
> },
> {
> "children": [
> {
> "meta": {
> "name": "Label"
> },
> "props": {
> "text": "Clipboard Contents"
> },
> "type": "ia.display.label"
> },
> {
> "meta": {
> "name": "Markdown"
> },
> "position": {
> "grow": 1
> },
> "propConfig": {
> "props.source": {
> "binding": {
> "config": {
> "path": "view.custom.clipboard"
> },
> "type": "property"
> }
> }
> },
> "type": "ia.display.markdown"
> }
> ],
> "meta": {
> "name": "FlexContainer"
> },
> "position": {
> "height": 200,
> "width": 200,
> "x": 499,
> "y": 211
> },
> "props": {
> "direction": "column"
> },
> "type": "ia.container.flex"
> },
> {
> "children": [
> {
> "meta": {
> "name": "Label"
> },
> "props": {
> "text": "This"
> },
> "type": "ia.display.label"
> },
> {
> "meta": {
> "name": "Label_0"
> },
> "props": {
> "text": "That"
> },
> "type": "ia.display.label"
> },
> {
> "meta": {
> "name": "Label_1"
> },
> "props": {
> "text": "Those"
> },
> "type": "ia.display.label"
> },
> {
> "meta": {
> "name": "Label_2"
> },
> "props": {
> "text": "Other"
> },
> "type": "ia.display.label"
> }
> ],
> "meta": {
> "name": "FlexContainer_0"
> },
> "position": {
> "height": 200,
> "width": 200,
> "x": 200,
> "y": 211
> },
> "props": {
> "direction": "column",
> "style": {
> "gap": "1rem"
> }
> },
> "type": "ia.container.flex"
> }
> ],
> "meta": {
> "name": "root"
> },
> "type": "ia.container.coord"
> }
> }
> 
> ```

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 5:48am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/7 "2025-01-10T05:48:48Z")

</div>

Thanks..  
I installed the periscope module..

And ran into this error in gateway log:  
`Exception occurred executing client-side JavaScript.`

in Firefox Console:  
`store.Channel: Uh oh, received message for unknown protocol "periscope-js-run"`

What did I missed?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 6:13am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/8 "2025-01-10T06:13:36Z")

</div>

Is it possible your client session is out of date (i.e. the session was created before the module was installed)?

If so, try closing and reopening your browser.

---

<div class="post-metadata">

**Author:** ![liam1](https://avatars.discourse-cdn.com/v4/letter/l/e68b1a/32.png) [@liam1](https://forum.inductiveautomation.com/u/liam1)\
**Post date:** [January 10, 2025, 6:18am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/9 "2025-01-10T06:18:57Z")

</div>

> [@bmusson](#):
>
> ```python
> def withClipboard(clipboard):
> self.view.custom.clipboard = clipboard
> 		
> 	
> system.perspective.runJavaScriptAsync('''async () => {
> return await navigator.clipboard.readText()
> }''', {}, withClipboard)
> 
> ```

I am getting this error:

```python
CompletionException: com.mussonindustrial.embr.perspective.common.exceptions.JavaScriptExecutionException: Error running client-side JavaScript.
	caused by JavaScriptExecutionException: Error running client-side JavaScript.
	caused by JavaScriptException: TypeError: Cannot read properties of undefined (reading 'readText')
	caused by JavaScriptException: TypeError: Cannot read properties of undefined (reading 'readText')
    at AsyncFunction.eval [as runNamed] (eval at ks (http://10.10.10.100:8088/res/embr-periscope/embr-periscope-client.js:1:1071), <anonymous>:4:36)
    at http://10.10.10.100:8088/res/embr-periscope/embr-periscope-client.js:31:6434
    at new Promise (<anonymous>)
    at http://10.10.10.100:8088/res/embr-periscope/embr-periscope-client.js:31:6387
    at z.processMessage (http://10.10.10.100:8088/res/perspective/js/PerspectiveClient.4c66478bafda3caeebe1.js:2:473641)

Ignition v8.1.41 (b2024052809)
Java: Azul Systems, Inc. 17.0.10

```

I notice that the browser does not prompt to allow clipboard access, could this be the issue?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 6:23am UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/10 "2025-01-10T06:23:23Z")

</div>

According to MDN, the navigator object is only usable in secure contexts (over HTTPS). Edit: `localhost` also counts as a secure context.

> **[Navigator: clipboard property - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard#browser_compatibility)**
>
> The clipboard read-only property of the Navigator interface returns a Clipboard object used to read and write the clipboard's contents.

Also, the first time I ran the script I got a pop-up from Chrome asking to allow clipboard access. I’d assume it’d be the same for Firefox, but I can’t say for sure.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 4:06pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/11 "2025-01-10T16:06:02Z")

</div>

Same error displayed by both Firefox and Chrome,  
`store.Channel: Uh oh, received message for unknown protocol "periscope-js-run`

Seems to me that, browser does not recognize the instruction coming from periscope module..? Why is this?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 4:52pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/12 "2025-01-10T16:52:50Z")

</div>

> [@Eugene49](#):
>
> Same error displayed by both Firefox and Chrome,  
> `store.Channel: Uh oh, received message for unknown protocol "periscope-js-run`

1. What version of Ignition, and what version of Periscope?
2. Anything in the gateway logs when you restart the module?
3. What happens if you run `window.__client.connection.handlers.get("periscope-js-run")` in Firefox/Chrome's console window?

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 4:59pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/13 "2025-01-10T16:59:03Z")

</div>

Also, can you show your `system.perspective.runJavaScriptAsync/Blocking` code? Where are you calling it from?

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 5:10pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/14 "2025-01-10T17:10:15Z")

</div>

Ignition Version 8.1.37  
Periscope Module 0.5.1

There is no `window.__client`

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/8/a8dbc8f39626d2a4975db3b0e3140fb1cc196fba.png)

The only error in gateway is this:

```python
java.util.concurrent.CompletionException: java.util.concurrent.TimeoutException
at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.completeThrowable(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture$UniAccept.tryFire(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.postComplete(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(Unknown Source)
at java.base/java.util.concurrent.CompletableFuture$Timeout.run(Unknown Source)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.util.concurrent.TimeoutException: null
... 7 common frames omitted

```

Which relates to client timing out, because no response was received.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 5:17pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/15 "2025-01-10T17:17:36Z")

</div>

> [@Perspective - Accessing images on the clipboard](https://forum.inductiveautomation.com/t/perspective-accessing-images-on-the-clipboard/41367/20):
>
> Also, can you show your `system.perspective.runJavaScriptAsync/Blocking` code? Where are you calling it from?

I exactly use the "view's" json you posted.

> [@bmusson](#):
>
> What happens if you run `window.__client.connection.handlers.get("periscope-js-run")` in Firefox/Chrome's console window?

My bad, this command exist in chrome:  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/f/6f626994207def62c47ef132d3d25fe74fb61e3a.png)

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 5:36pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/16 "2025-01-10T17:36:25Z")

</div>

> [@Eugene49](#):
>
> There is no `window.__client`

It looks like you ran `window.__client.connection.handlers.get("periscope-js-run")` on a non-Perspective tab, correct?

Either way, I can reproduce the issue on 8.1.37.

It looks like you _must_ have at least one of Periscope's components on a view for the `periscope-js-run` handler to be installed. It doesn't have to be on every view, just **a** view.

So, to work around this for now, create a new view in your project and add an `Embedded View +`, `Flex Repeater +`, or `Swiper` component. After saving, the JavaScript page handlers should now install themselves when the client starts.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 5:41pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/17 "2025-01-10T17:41:19Z")

</div>

Worked now as per your video..  
I added a component from the module.  
Thank you for helping me troubleshoot..

Now.. The real thing.  
Do you have direction, how to handle images on the clipboard..?  
How do I get the byte from navigator.clipboard.read()

I saw this sample code from navigator clipboard doc online:

```python
const destinationImage = document.querySelector("#destination");
destinationImage.addEventListener("click", pasteImage);

async function pasteImage() {
  try {
    const clipboardContents = await navigator.clipboard.read();
    for (const item of clipboardContents) {
      if (!item.types.includes("image/png")) {
        throw new Error("Clipboard does not contain PNG image data.");
      }
      const blob = await item.getType("image/png");
      destinationImage.src = URL.createObjectURL(blob);
    }
  } catch (error) {
    log(error.message);
  }
}

```

This is a good start.  
Appreciate if someone can marinate this with Jython. 🍺

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 6:00pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/18 "2025-01-10T18:00:48Z")

</div>

> [@Eugene49](#):
>
> I added a component from the module.  
> Thank you for helping me troubleshoot..

Great! I’ll get this fixed in the next release.

> [@Eugene49](#):
>
> Do you have direction, how to handle images on the clipboard..?

Unfortunately I don’t, maybe some else around here can help.

You should be able to return the image bytes from `runJavaScriptAsync`, but depending on the size of the images it’s probably not a good idea. A better idea would be to POST the image bytes to a WebDev endpoint, then return the response of the POST.

---

<div class="post-metadata">

**Author:** ![Eugene49](https://avatars.discourse-cdn.com/v4/letter/e/c6cbf5/32.png) [@Eugene49](https://forum.inductiveautomation.com/u/Eugene49)\
**Post date:** [January 10, 2025, 6:50pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/19 "2025-01-10T18:50:04Z")

</div>

I hit a wall.

From google, cannot convert image data from clipboard to byte array.

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [January 10, 2025, 6:58pm UTC](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741/20 "2025-01-10T18:58:51Z")

</div>

> **[How to retrieve images from the clipboard with JavaScript in the Browser](https://ourcodeworld.com/articles/read/491/how-to-retrieve-images-from-the-clipboard-with-javascript-in-the-browser)**
>
> Learn how to retrieve the clipboard content as an image with JavaScript in the browser.

[Next page](https://forum.inductiveautomation.com/t/accessing-clipboard-in-perspective/100741.md?page=2)
