Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved WebGL + TMP = copy paste issues

Discussion in 'UGUI & TextMesh Pro' started by blakeburns13, Jun 15, 2020.

  1. blakeburns13

    blakeburns13

    Joined:
    Mar 20, 2018
    Posts:
    3
    Unity version 2019.2.20f1

    Using the code below, I am gaining access to the system clipboard through the browser in order to copy paste in WebGL into/out of a Text Mesh Pro Input Field. I believe TMP has its own internal copy paste functionality that causes issues when I have something in the system clipboard, and also in the internal app clipboard that TMP uses.

    Does anyone know a solution for this?

    TMPCopyPasteController.cs - added to a TMP input field

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UI;
    6. using TMPro;
    7.  
    8. public class TMPCopyPasteController : UIController {
    9.     public TMPro.TMP_InputField inputField;
    10.  
    11.     #if UNITY_WEBGL && !UNITY_EDITOR
    12.  
    13.     public void Update()
    14.     {
    15.         // Early out
    16.         if(!Input.GetKeyDown(KeyCode.C) && !Input.GetKeyDown(KeyCode.V) && !Input.GetKeyDown(KeyCode.X))
    17.             return;
    18.  
    19.         /* COPY TO CLIPBOARD  */
    20.  
    21.         if( Input.GetKeyDown(KeyCode.C)
    22.             &&
    23.             ( Input.GetKey(KeyCode.LeftCommand)
    24.                 || Input.GetKey(KeyCode.RightCommand)
    25.                 || Input.GetKey(KeyCode.LeftControl)
    26.                 || Input.GetKey(KeyCode.RightControl) ))
    27.         {
    28.             string selectedText = GetSelectedText();
    29.             if(selectedText.Length > 0)
    30.             {
    31.                 WebGLClipboard.CopyToWebGLClipboard(selectedText);
    32.             }
    33.         }
    34.  
    35.         /* CUT TO CLIPBOARD
    36.            This is necessary because cutting is something we do in-app so we use our built-in clipboard */
    37.  
    38.         if( Input.GetKeyDown(KeyCode.X)
    39.             &&
    40.             ( Input.GetKey(KeyCode.LeftCommand)
    41.                 || Input.GetKey(KeyCode.RightCommand)
    42.                 || Input.GetKey(KeyCode.LeftControl)
    43.                 || Input.GetKey(KeyCode.RightControl) ))
    44.         {
    45.             string selectedText = GetSelectedText();
    46.             if(selectedText.Length > 0)
    47.             {
    48.                 WebGLClipboard.CopyToWebGLClipboard(selectedText);
    49.                 ClearSelectedText();
    50.             }
    51.         }
    52.  
    53.         /* PASTE FROM CLIPBOARD  */
    54.  
    55.         if( Input.GetKeyDown(KeyCode.V)
    56.             &&
    57.             ( Input.GetKey(KeyCode.LeftCommand)
    58.                 || Input.GetKey(KeyCode.RightCommand)
    59.                 || Input.GetKey(KeyCode.LeftControl)
    60.                 || Input.GetKey(KeyCode.RightControl) ))
    61.         {
    62.             //Debug.Log("Clipboard text = " + ToSClipboard.Clipboard);
    63.            
    64.             try {
    65.                 WebGLClipboard.PasteFromWebGLClipboard(inputField.name, handler: "HandlePasteFromWebGLClipboard");
    66.             }
    67.             catch (Exception) {
    68.             }
    69.         }
    70.     }
    71.  
    72.     private string GetSelectedText()
    73.     {
    74.         int a = inputField.selectionStringAnchorPosition;
    75.         int b = inputField.selectionStringFocusPosition;
    76.  
    77.         //Debug.Log("a = " + a.ToString() + " : b = " + b.ToString());
    78.         string selectedText = inputField.text.Substring(Math.Min(a, b), Math.Max(a, b) - Math.Min(a, b));
    79.  
    80.         Debug.Log("Selected text = " + selectedText);
    81.  
    82.         return selectedText;
    83.     }
    84.  
    85.     private void ClearSelectedText()
    86.     {
    87.         if(inputField.text.Length > 0)
    88.         {
    89.             int currentCursorPosition = inputField.stringPosition;
    90.             int a = inputField.selectionStringAnchorPosition;
    91.             int b = inputField.selectionStringFocusPosition;
    92.             bool isTextSelected = (a != b);
    93.  
    94.             if(isTextSelected)
    95.             {
    96.                 // Get the text on each side of the text to be cleared
    97.                 string pre = inputField.text.Substring(0, Math.Min(a, b));
    98.                 string post = inputField.text.Substring(Math.Max(a, b));
    99.                 inputField.text = pre + post;
    100.                 // Set the carat position to the beginning of the text we deleted
    101.                 inputField.stringPosition = pre.Length;
    102.             }
    103.         }
    104.     }
    105.  
    106. #endif
    107.  
    108.     public void HandlePasteFromWebGLClipboard(string pasteValue) {
    109. #if UNITY_WEBGL && !UNITY_EDITOR
    110.        
    111.         if(inputField.isFocused == true && inputField.interactable)
    112.         {
    113.             if(inputField.text.Length > 0)
    114.             {
    115.                 int currentCursorPosition = inputField.stringPosition;
    116.                 int a = inputField.selectionStringAnchorPosition;
    117.                 int b = inputField.selectionStringFocusPosition;
    118.                 bool isTextSelected = (a != b);
    119.  
    120.                 if(isTextSelected)
    121.                 {
    122.                     // Overwrite the selected text
    123.                     string pre = inputField.text.Substring(0, Math.Min(a, b));
    124.                     string post = inputField.text.Substring(Math.Max(a, b));
    125.                     inputField.text = pre + pasteValue + post;
    126.                     // Set the carat position to the end of the text we inserted
    127.                     inputField.stringPosition = pre.Length + pasteValue.Length;
    128.                 }
    129.                 else
    130.                 {
    131.                     // No text is selected, so use the current position of the cursor in the input field
    132.                     // to insert the clipboard text
    133.                     string pre = inputField.text.Substring(0, currentCursorPosition);
    134.                     string post = inputField.text.Substring(currentCursorPosition);
    135.                     inputField.text = pre + pasteValue + post;
    136.                     // Set the carat position to the end of the text we inserted
    137.                     inputField.stringPosition = currentCursorPosition + pasteValue.Length;
    138.                 }
    139.             }
    140.             else
    141.             {
    142.                 // Simplest case is our inputfield is empty so just set the text
    143.                 inputField.text = pasteValue;
    144.  
    145.                 // Set the carat position to the end of the text we inserted
    146.                 int currentCursorPosition = inputField.stringPosition;
    147.                 inputField.stringPosition = currentCursorPosition + pasteValue.Length;
    148.             }
    149.  
    150.             if(inputField.characterLimit < inputField.text.Length) {
    151.                 inputField.text = inputField.text.Substring(0, inputField.characterLimit);
    152.             }
    153.         }
    154. #endif
    155.     }
    156.  
    157. }
    158.  
    159.  
    WebGLClipboard.cs - controls our access to the system clipboard

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using System.Runtime.InteropServices;
    4. using AOT;
    5.  
    6. public class WebGLClipboard {
    7.  
    8. #if UNITY_WEBGL && !UNITY_EDITOR
    9.     [DllImport("__Internal")]
    10.     public static extern void ClipboardReader(string gObj, string vName);
    11.  
    12.     [DllImport("__Internal")]
    13.     public static extern void ClipboardWriter(string newClipText);
    14. #endif
    15.  
    16.     public static void CopyToWebGLClipboard(string newClipText) {
    17. #if UNITY_WEBGL && !UNITY_EDITOR
    18.         ClipboardWriter(newClipText);
    19. #endif
    20.     }
    21.  
    22. #if UNITY_WEBGL && !UNITY_EDITOR
    23.     [MonoPInvokeCallback(typeof(Action))]
    24. #endif
    25.     public static void PasteFromWebGLClipboard(string gameObjectName, string handler) {
    26. #if UNITY_WEBGL && !UNITY_EDITOR
    27.         ClipboardReader(gameObjectName, handler);  
    28. #endif
    29.     }
    30. }
    PastePlugin.jslib - javascript library to read system clipboard

    Code (JavaScript):
    1. var PastePlugin = {
    2.   ClipboardReader: function(gObj, vName) {
    3.     try {
    4.       var gameObjectName = UTF8ToString(gObj);
    5.       var voidName = UTF8ToString(vName);
    6.       navigator.clipboard.readText().then(function(data) {
    7.         gameInstance.SendMessage(gameObjectName, voidName, data);
    8.       })
    9.     } catch (e) {
    10.       // Clipboard API not available
    11.         console.log('Failed to read clipboard contents. Clipboard API not available on this browser.');
    12.     }
    13.   }
    14. };
    15. mergeInto(LibraryManager.library, PastePlugin);
    CopyPlugin .jslib - javascript library to write to the system clipboard

    Code (JavaScript):
    1. var CopyPlugin = {
    2.   ClipboardWriter: function(newClipText) {
    3.       try {
    4.         var clipText = UTF8ToString(newClipText);
    5.       navigator.clipboard.writeText(clipText);
    6.     } catch (e) {
    7.       // Clipboard API not available
    8.          console.log('Failed to write to clipboard. Clipboard API not available on this browser.');
    9.     }
    10.   }
    11. };
    12. mergeInto(LibraryManager.library, CopyPlugin);
     
  2. blakeburns13

    blakeburns13

    Joined:
    Mar 20, 2018
    Posts:
    3
    The issue we were having is that we are trying to access the external clipboard in order to paste into a TMP text field, which worked well with the above code. However, once a user had something in their external AND internal clipboards, it would be pasted twice. We decided to get the source TMP code and do our external copy pasting directly in TMP which solved our issue.
     
  3. ujz

    ujz

    Joined:
    Feb 17, 2020
    Posts:
    28
    Thanks for sharing this. It works well on Firefox, but throws NotAllowedError on other browsers like Safari iOS. I'm not versed in web dev, but it seems the clipboard API requires more finesse, including additional popup buttons to capture the copy.

    Were you able to get this to work on mobile browsers? Thanks again for sharing.