Skip to content
This repository has been archived by the owner on Aug 16, 2021. It is now read-only.

feat: clipboard access #1300

Merged
merged 14 commits into from
Sep 24, 2020

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "Clipboard",
"references": [
"GUID:b1087c5731ff68448a0a9c625bb7e52d"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using DCL.Helpers;

public class Clipboard
{
private readonly Queue<Promise<string>> promises = new Queue<Promise<string>>();
private readonly IClipboardHandler handler = null;

/// <summary>
/// Create a platform specific instance of Clipboard
/// </summary>
/// <returns>Clipboard instance</returns>
public static Clipboard Create()
{
#if UNITY_WEBGL && !UNITY_EDITOR
return new Clipboard(ClipboardWebGL.i);
#else
return new Clipboard(new ClipboardStandalone());
#endif
}

/// <summary>
/// Push a string value to the clipboard
/// </summary>
/// <param name="text">string to store</param>
public void WriteText(string text)
{
handler?.RequestWriteText(text);
}

/// <summary>
/// Request the string stored at the clipboard
/// </summary>
/// <returns>Promise of the string value stored at clipboard</returns>
[Obsolete("Firefox not supported")]
public Promise<string> ReadText()
{
Promise<string> promise = new Promise<string>();
promises.Enqueue(promise);
handler?.RequestGetText();
return promise;
}

public Clipboard(IClipboardHandler handler)
{
this.handler = handler;
handler.Initialize(OnReadText);
}

private void OnReadText(string text, bool error)
{
while (promises.Count > 0)
{
var promise = promises.Dequeue();
if (error)
{
promise.Reject(text);
}
else
{
promise.Resolve(text);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

public interface IClipboardHandler
{
void Initialize(Action<string, bool> onRead);
void RequestWriteText(string text);
void RequestGetText();
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using UnityEngine;

internal class ClipboardStandalone : IClipboardHandler
{
private Action<string, bool> OnRead;

void IClipboardHandler.Initialize(Action<string, bool> onRead)
{
this.OnRead = onRead;
}

void IClipboardHandler.RequestWriteText(string text)
{
GUIUtility.systemCopyBuffer = text;
}

void IClipboardHandler.RequestGetText()
{
OnRead?.Invoke(GUIUtility.systemCopyBuffer, false);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Runtime.InteropServices;
using AOT;
using DCL;
using UnityEngine;

internal class ClipboardWebGL : Singleton<ClipboardWebGL>, IClipboardHandler, IDisposable
{
private Action<string, bool> OnRead;
private bool copyInput = false;

private delegate void ReadTextCallback(IntPtr ptrText, int intError);

private delegate void OnPasteInputCallback(IntPtr ptrText);

private delegate void OnCopyInputCallback();

[DllImport("__Internal")]
private static extern void initialize(Action<IntPtr, int> readTextCallback, Action<IntPtr> pasteCallback,
Action copyCallback);

/// <summary>
/// External call to write text in the browser's clipboard
/// </summary>
/// <param name="text">string to push to the clipboard</param>
[DllImport("__Internal")]
private static extern void writeText(string text);

/// <summary>
/// External call to request the string value stored at browser's clipboard
/// </summary>
[DllImport("__Internal")]
private static extern void readText();

/// <summary>
/// This static function is called from the browser. It will receive a pointer to the string value
/// stored at browser's clipboard or and error if it couldn't get the value
/// </summary>
/// <param name="ptrText">pointer to the clipboard's string value</param>
/// <param name="intError">0 if error, other if OK</param>
[MonoPInvokeCallback(typeof(ReadTextCallback))]
private static void OnReceiveReadText(IntPtr ptrText, int intError)
{
string value = Marshal.PtrToStringAuto(ptrText);
bool error = intError == 0;
i?.OnRead?.Invoke(value, error);
}

/// <summary>
/// This static function is called from the browser. It will be called when a PASTE input is performed (CTRL+V)
/// and it will receive a pointer to the string value stored at browser's clipboard
/// </summary>
/// <param name="ptrText">pointer to the clipboard's string value</param>
[MonoPInvokeCallback(typeof(OnPasteInputCallback))]
private static void OnReceivePasteInput(IntPtr ptrText)
{
string value = Marshal.PtrToStringAuto(ptrText);
// NOTE: after marshalling we overwrite unity's clipboard buffer with the value coming from the browser
GUIUtility.systemCopyBuffer = value;
}

/// <summary>
/// This static function is called from the browser. It will be called when a COPY input is performed (CTRL+C)
/// </summary>
[MonoPInvokeCallback(typeof(OnCopyInputCallback))]
private static void OnReceiveCopyInput()
{
// NOTE: here we set the flag that a copy input was performed to be used in OnBeforeRender function
if (i != null) i.copyInput = true;
}

public ClipboardWebGL()
{
Application.onBeforeRender += OnBeforeRender;
}

public void Dispose()
{
Application.onBeforeRender -= OnBeforeRender;
}

void IClipboardHandler.Initialize(Action<string, bool> onRead)
{
this.OnRead = onRead;
initialize(OnReceiveReadText, OnReceivePasteInput, OnReceiveCopyInput);
}

void IClipboardHandler.RequestWriteText(string text)
{
writeText(text);
}

void IClipboardHandler.RequestGetText()
{
readText();
}

void OnBeforeRender()
{
// NOTE: before rendering (just after Unity's input is processed) we check if there was a COPY input (CTRL+C) triggered by
// the browser. If there was a COPY input we push the text copied and stored inside Unity's clipboard into the browser's clipboard.
// It is done this way cause we don't have a callback for Unity's copy input and because we want to store the value in
// browser's clipboard so we are able to paste it outside Unity's "sandboxing"
if (copyInput)
{
copyInput = false;
writeText(GUIUtility.systemCopyBuffer);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var ClipboardWebGL = {
initialize: function(csReadCallback, csPasteCallback, csCopyCallback){
window.clipboardReadText = function (text, error){
const bufferSize = lengthBytesUTF8(text) + 1
const ptrText = _malloc(bufferSize)
stringToUTF8(text, ptrText, bufferSize)
const intError = error? 0 : 1
Runtime.dynCall('vii', csReadCallback, [ptrText, intError])
}

window.addEventListener('paste', function(e) {
const text = e.clipboardData.getData('text')
const bufferSize = lengthBytesUTF8(text) + 1
const ptrText = _malloc(bufferSize)
stringToUTF8(text, ptrText, bufferSize)
Runtime.dynCall('vi', csPasteCallback, [ptrText])
})

window.addEventListener('copy', function(e) {
Runtime.dynCall('v', csCopyCallback)
})
},

writeText: function (text){
navigator.clipboard.writeText(Pointer_stringify(text))
},

readText: function (){
// NOTE: firefox does not support clipboard.read
if (navigator.clipboard.readText === undefined){
window.clipboardReadText("not supported", true)
return
}

// NOTE: workaround cause jslib don't support async functions
eval("navigator.clipboard.readText()" +
".then(text => window.clipboardReadText(text, false))"+
".catch(e => window.clipboardReadText(e.message, true))")
}
};
mergeInto(LibraryManager.library, ClipboardWebGL);
Loading