All Products
Search
Document Center

Cloud Phone:Paste in Safari with the Web SDK

Last Updated:Aug 27, 2026

Due to security restrictions, Safari triggers a native paste pop-up when accessing the clipboard. This degrades the user experience when syncing content to the Cloud Phone. This topic explains how to use the Web SDK to paste text from your local clipboard into Safari.

Solution overview

  1. Add a custom clipboard button to your local client. Clicking this button opens a pop-up input box.

  2. Enter the content to send to the Cloud Phone in the pop-up input box.

  3. Click the Paste Now button in the pop-up input box to sync the content to the clipboard and input field of the Cloud Phone.

Procedure

Cloud phone parameters

  • Set readClipboardDataByUser to true to prevent the SDK from automatically reading from the clipboard.

  • Set useCustomIme to true to enable the local input method editor (IME). This is required to sync content to the input field of the Cloud Phone.

// Check if the browser is Safari.
function isSafari() {
    return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
}
// Main control: Disable automatic clipboard reads and enable the local IME.
connConfig = {..., readClipboardDataByUser:isSafari(), useCustomIme:true}; 
var appInfo = {
  ...,
  connConfig: connConfig,
  ...
};
var sessionParam = {
  ...,
  appInfo: appInfo,
  ....
};
var wuyingSdk = Wuying.WebSDK;
session = wuyingSdk.createSession('appstream', sessionParam);
// Thumbnail configuration: Enable the local IME.
this.thumbnail = new window.Wuying.ThumbnailSDK({
  ...,
  connectionConfig: {
    useCustomIme: true,
  },
},
{
  onConnected: (data) => {
    this.thumbnail.session.getLocalConfig().setClipboardEnabled(true);
  },
  onDisConnected: (data) => {},
  onThumbnailData: (url) => {},
}
);

Build the pop-up input box

Create a clipboard button that opens the pop-up input box. The following code provides an example.

function showInput() {
  showCustomDialog();
  // Close the dialog when the overlay is clicked.
  document.querySelector('.modal-overlay').addEventListener('click', function (e) {
      if (e.target === this) {
          closeModal();
      }
  });

  // Close the dialog when the Escape key is pressed.
  document.addEventListener('keydown', function (e) {
      if (e.key === 'Escape') {
          closeModal();
      }
  });
}

function closeModal() {
  console.log('closeModal');
  const overlay = document.querySelector('.modal-overlay');
  if (overlay) {
      overlay.style.display = 'none';
      overlay.remove();
  }
}

function submitContent() {
  const content = document.querySelector('.input-field').value.trim();
  if (!content) {
      alert('Please enter content.');
      return;
  }
  // Sync the content to the Cloud Phone.
  sendMsgToCloudPhoneOperation(content);
  closeModal();
}

function showCustomDialog() {
  // Create the modal overlay.
  const overlay = document.createElement('div');
  overlay.className = 'modal-overlay';
  // Ensure the modal captures all events and is displayed at the highest level
  overlay.style.pointerEvents = 'all';
  overlay.style.zIndex = '9999';
  
  // Prevent events from propagating to underlying layers
  const stopPropagation = function(e) {
      e.stopPropagation();
  };
  
  overlay.addEventListener('contextmenu', stopPropagation, true);
  
  // Add event listeners to intercept all keyboard events
  overlay.addEventListener('keydown', stopPropagation, true);
  overlay.addEventListener('keyup', stopPropagation, true);
  overlay.addEventListener('keypress', stopPropagation, true);
  
  overlay.innerHTML = `
      <div class="modal">
          <div class="modal-header">
              <h2 class="modal-title">Clipboard</h2>
              <button class="close-btn" onclick="closeModal()">×</button>
          </div>
          
          <div class="input-container">
              <textarea 
                  class="input-field" 
                  placeholder="Enter content"
                  maxlength="500"
              ></textarea>
          </div>
          
          <button class="submit-btn" onclick="submitContent()">
              Paste Now
          </button>
      </div>
  `;

  document.body.appendChild(overlay);
  
  // Focus the textarea when the modal opens to ensure keyboard events are captured
  setTimeout(() => {
      const textarea = overlay.querySelector('.input-field');
      if (textarea) {
          textarea.focus();
      }
  }, 100);
}

Send text to the Cloud Phone

Trigger the sync task

function submitContent() {
  const content = document.querySelector('.input-field').value.trim();
  if (!content) {
      alert('Please enter content.');
      return;
  }
  // Sync the content to the Cloud Phone.
  sendMsgToCloudPhoneOperation(content);
  closeModal();
}

Sync content to the Cloud Phone

function sendMsgToCloudPhoneOperation(msg) {
  // For the main control session
  if (session) {
    session.setClipboardModule('sendClipboardDataToRemote', msg) 
  }
  // For thumbnail sessions
  for (const [key, value] of thumbnailSDKMap) {
      console.log('thumbnail sendMsgToCloudPhoneOperation ', key, msg);
      value.thumbnail.session.getClipboardModule().sendClipboardDataToRemote(msg);
  }
}

Usage notes

The custom dialog must capture all keyboard events to prevent other UI elements from intercepting them. This ensures that key presses register correctly within the dialog.

overlay.addEventListener('contextmenu', stopPropagation, true);

// Add event listeners to intercept all keyboard events
overlay.addEventListener('keydown', stopPropagation, true);
overlay.addEventListener('keyup', stopPropagation, true);
overlay.addEventListener('keypress', stopPropagation, true);